Поиск…


Вступление

Указатель - это тип переменной, который может хранить адрес другого объекта или функции.

Синтаксис

  • <Тип данных> * <Имя переменной>;
  • int * ptrToInt;
  • void * ptrToVoid; / * C89 + * /
  • struct someStruct * ptrToStruct;
  • int ** ptrToPtrToInt;
  • int arr [длина]; int * ptrToFirstElem = arr; / * Для <C99 'length' должна быть константа времени компиляции, для> = C11 она может быть одной. * /
  • int * arrayOfPtrsToInt [длина]; / * Для <C99 'length' должна быть константа времени компиляции, для> = C11 она может быть одной. * /

замечания

Позиция звездочки не влияет на смысл определения:

/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;

Однако при определении нескольких указателей сразу требуется каждая из них:

int *i, *j; /* i and j are both pointers */
int* i, j;  /* i is a pointer, but j is an int not a pointer variable */

Также возможен массив указателей, где перед именем переменной массива задается звездочка:

int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */

Общие ошибки

Неправильное использование указателей часто является источником ошибок, которые могут включать ошибки безопасности или сбои программ, чаще всего из-за сбоев сегментации.

Не проверять наличие сбоев

Выделение памяти не гарантируется, и вместо этого может возвращать указатель NULL . Используя возвращаемое значение, не проверяя, выполнено ли выделение, вызывает неопределенное поведение . Это обычно приводит к сбою, но нет никакой гарантии, что произойдет сбой, поэтому полагаться на это также может привести к проблемам.

Например, небезопасный способ:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

Безопасный способ:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

Использование литеральных чисел вместо sizeof при запросе памяти

Для конкретной конфигурации компилятора / машины типы имеют известный размер; однако нет никакого стандарта, который определяет, что размер данного типа (кроме char ) будет одинаковым для всех конфигураций компилятора / машины. Если код использует 4 вместо sizeof(int) для распределения памяти, он может работать на исходном компьютере, но код не обязательно переносится на другие машины или компиляторы. Фиксированные размеры для типов должны быть заменены sizeof(that_type) или sizeof(*var_ptr_to_that_type) .

Не переносное распределение:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

Переносное распределение:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

Или, еще лучше:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

Утечка памяти

Невыполнение выделенной памяти с помощью free ведет к созданию памяти без повторного использования, которая больше не используется программой; это называется утечкой памяти . Память утечки ресурсов памяти отходов и может привести к сбоям распределения.

Логические ошибки

Все распределения должны соответствовать одному и тому же шаблону:

  1. Выделение с использованием malloc (или calloc )
  2. Использование для хранения данных
  3. Де-распределение с использованием free

Несоблюдение этого шаблона, например, использование памяти после вызова free ( висячего указателя ) или перед вызовом malloc ( дикий указатель ), вызов free дважды («double free») и т. Д. Обычно вызывает ошибку сегментации и приводит к краху программы.

Эти ошибки могут быть временными и трудно отлаживать - например, освобожденная память обычно не сразу восстанавливается ОС, и поэтому оборванные указатели могут сохраняться некоторое время и, похоже, работают.

В системах, где это работает, Valgrind - бесценный инструмент для определения того, какая память просочилась и где она была изначально выделена.

Создание указателей на стек переменных

Создание указателя не продлевает срок действия указанной переменной. Например:

int* myFunction() 
{
    int x = 10;
    return &x;
}

Здесь x имеет время автоматического хранения (обычно называемое распределением стека ). Поскольку он выделяется в стеке, его время жизни остается до тех пор, пока выполняется myFunction ; после выхода myFunction переменная x будет уничтожена. Эта функция получает адрес x (с использованием &x ) и возвращает его вызывающему, оставляя вызывающего абонента указателем на несуществующую переменную. Попытка доступа к этой переменной затем вызовет неопределенное поведение .

Большинство компиляторов фактически не очищают кадр стека после выхода функции, поэтому разыменование возвращаемого указателя часто дает вам ожидаемые данные. Однако при вызове другой функции указываемая память может быть перезаписана, и, похоже, поврежденные данные были повреждены.

Чтобы устранить это, либо malloc хранилище для возвращаемой переменной, либо верните указатель на вновь созданное хранилище, либо потребуйте, чтобы действительный указатель был передан функции вместо того, чтобы возвращать ее, например:

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %i\n", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %i\n", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

Приращение / декремент и разыменование

Если вы пишете *p++ чтобы увеличить то, что указано p , вы ошибаетесь.

Приращение / декрементирование сообщения выполняется до разыменования. Поэтому это выражение будет увеличивать указатель p и возвращать то, что было указано p до того, как он увеличился, не меняя его.

Вы должны написать (*p)++ для увеличения того, на что указывает p .

Это правило также применяется к пост декрементированию: *p-- будет декрементировать указатель p , а не то, что указано p .

Выделение указателя

int a = 1;
int *a_pointer = &a;

Чтобы разыменовать a_pointer и изменить значение a, мы используем следующую операцию

*a_pointer = 2;

Это можно проверить, используя следующие операторы печати.

printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */

Однако можно было бы ошибаться, чтобы разыменовать NULL или иначе недействительный указатель. это

int *p1, *p2;

p1 = (int *) 0xbad;
p2 = NULL;

*p1 = 42;
*p2 = *p1 + 1;

обычно является неопределенным поведением . p1 не может быть разыменован, поскольку он указывает на адрес 0xbad который не может быть действительным адресом. Кто знает, что там? Это может быть операционная память системы или память другой программы. Единственный временный код, подобный этому, используется во встроенной разработке, которая хранит определенную информацию по жестко закодированным адресам. p2 не может быть разыменован, поскольку он имеет NULL , что является недопустимым.

Выделение указателя на структуру

Допустим, у нас есть следующая структура:

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

Мы можем определить MY_STRUCT чтобы опустить ключевое слово struct поэтому нам не нужно набирать struct MY_STRUCT каждый раз, когда мы его используем. Это, однако, не является обязательным.

typedef struct MY_STRUCT MY_STRUCT;

Если у нас есть указатель на экземпляр этой структуры

MY_STRUCT *instance;

Если этот оператор появляется в области файлов, instance будет инициализирован нулевым указателем при запуске программы. Если этот оператор появляется внутри функции, его значение не определено. Переменная должна быть инициализирована, чтобы указывать на действительную переменную MY_STRUCT или на динамически распределенное пространство, прежде чем она может быть разыменована. Например:

MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;

Когда указатель действителен, мы можем разыменовать его для доступа к его членам с использованием одного из двух разных обозначений:

int a = (*instance).my_int;
float b = instance->my_float;

Хотя оба эти метода работают, лучше использовать оператор arrow -> а не комбинацию круглых скобок, оператора разыменования * и точки . потому что его легче читать и понимать, особенно с вложенными приложениями.

Другое важное различие показано ниже:

MY_STRUCT copy = *instance;
copy.my_int    = 2;

В этом случае copy содержит копию содержимого instance . Изменение my_int copy не изменит его в instance .

MY_STRUCT *ref = instance;
ref->my_int    = 2;

В этом случае ref является ссылкой на instance . Изменение my_int с использованием ссылки изменит его в instance .

Общепринятой практикой является использование указателей для структур как параметров в функциях, а не самих структур. Использование структур в качестве параметров функции может привести к переполнению стека, если структура велика. Использование указателя на структуру использует только достаточное пространство стека для указателя, но может вызвать побочные эффекты, если функция изменяет структуру, которая передается в функцию.

Указатели функций

Указатели также могут использоваться для указания функций.

Возьмем основную функцию:

int my_function(int a, int b)
{
    return 2 * a + 3 * b;
}

Теперь давайте определим указатель на тип этой функции:

int (*my_pointer)(int, int);

Чтобы создать его, просто используйте этот шаблон:

return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)

Затем мы должны назначить этот указатель на функцию:

my_pointer = &my_function;

Этот указатель теперь можно использовать для вызова функции:

/* Calling the pointed function */
int result = (*my_pointer)(4, 2);

...

/* Using the function pointer as an argument to another function */
void another_function(int (*another_pointer)(int, int))
{
    int a = 4;
    int b = 2;
    int result = (*another_pointer)(a, b);

    printf("%d\n", result);
}

Хотя этот синтаксис кажется более естественным и согласованным с базовыми типами, указатели на функции присвоения и разыменования не требуют использования операторов & и * . Таким образом, следующий снипп одинаково справедлив:

/* Attribution without the & operator */
my_pointer = my_function;

/* Dereferencing without the * operator */
int result = my_pointer(4, 2);

Чтобы повысить читаемость указателей на функции, можно использовать typedefs.

typedef void (*Callback)(int a);

void some_function(Callback callback)
{
    int a = 4;
    callback(a);
}

Еще одна удобочитаемость заключается в том, что стандарт C позволяет упростить указатель функции в аргументах, подобных приведенным выше (но не в объявлении переменных), к тому, что выглядит как прототип функции; таким образом, для определений функций и деклараций можно эквивалентно использовать следующее:

void some_function(void callback(int))
{
    int a = 4;
    callback(a);
}

Смотрите также

Указатели функций

Инициализация указателей

Инициализация указателя - хороший способ избежать диких указателей. Инициализация проста и ничем не отличается от инициализации переменной.

#include <stddef.h>

int main()
{
    int *p1 = NULL; 
    char *p2 = NULL;
    float *p3 = NULL;

         /* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */

    ...
}    

В большинстве операционных систем, непреднамеренно используя указатель, который был инициализирован NULL , часто приводит к сбою программы немедленно, что позволяет легко определить причину проблемы. Использование неинициализированного указателя часто может вызывать затруднительные диагностические ошибки.

Внимание:

Результат разыменования указателя NULL не определен, поэтому он не обязательно приведет к сбою, даже если это естественное поведение операционной системы, в которой работает программа. Оптимизация компилятора может привести к сбою в сбое, вызвав возникновение сбоя до или после точки в исходном коде, в котором произошла ошибка разыменования нулевого указателя, или привести к неожиданному удалению частей кода, которые содержат разыменование нулевого указателя, из программы. Отладочные сборки обычно не будут демонстрировать это поведение, но это не гарантируется языковым стандартом. Также допускается другое неожиданное и / или нежелательное поведение.

Поскольку NULL никогда не указывает на переменную, на выделенную память или на функцию, ее безопасно использовать в качестве защитного значения.

Внимание:

Обычно NULL определяется как (void *)0 . Но это не означает, что назначенный адрес памяти равен 0x0 . Для получения дополнительной информации обратитесь к C-faq для NULL-указателей

Обратите внимание, что вы также можете инициализировать указатели, чтобы они содержали значения, отличные от NULL.

int i1;

int main()
{
   int *p1 = &i1;
   const char *p2 = "A constant string to point to";
   float *p3 = malloc(10 * sizeof(float));
}

Адрес-оператора (&)

Для любого объекта (т. Е. Переменной, массива, объединения, структуры, указателя или функции) оператор унарного адреса может использоваться для доступа к адресу этого объекта.

Предположим, что

int i = 1;              
int *p = NULL;

Итак, утверждение p = &i; , копирует адрес переменной i в указатель p .

Это выражается как p points to i .

printf("%d\n", *p); prints 1, значение i .

Арифметика указателей

См. Здесь: Арифметика указателей

void * указатели в качестве аргументов и возвращаемые значения для стандартных функций

К & Р

void* - это тип catch для типов указателей на типы объектов. Примером этого является использование функции malloc , которая объявляется как

void* malloc(size_t);

Тип возвращаемого указателя в void означает, что можно присвоить возвращаемое значение из malloc указателю на любой другой тип объекта:

int* vector = malloc(10 * sizeof *vector);

Обычно считается хорошей практикой, чтобы явным образом не вводить значения в указатели void и из них. В конкретном случае malloc() это связано с тем, что при явном приведении компилятор может в противном случае предположить, но не предупреждать о некорректном возвращаемом типе для malloc() , если вы забыли включить stdlib.h . Это также случай использования правильного поведения указателей пустот, чтобы лучше соответствовать принципу DRY (не повторяйте себя); сравните это со следующим, в котором следующий код содержит несколько ненужных дополнительных мест, где опечатка может вызвать проблемы:

int* vector = (int*)malloc(10 * sizeof int*);

Аналогично, такие функции, как

void* memcpy(void *restrict target, void const *restrict source, size_t size);

имеют свои аргументы, указанные как void * потому что адрес любого объекта, независимо от типа, может быть передан. Здесь также вызов не должен использовать листинг

unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);

Концентраторы

Одиночные указатели

  • Указатель на int

    Указатель может указывать на разные целые числа, а int может быть изменен с помощью указателя. Этот образец кода присваивает b для указания на int b затем изменяет значение b на 100 .

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • Указатель на const int

    Указатель может указывать на разные целые числа, но значение int не может быть изменено с помощью указателя.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • const указатель на int

    Указатель может указывать только на один int но значение int может быть изменено с помощью указателя.

    int a, b;
    int* const p = &b; /* OK as initialisation, no assignment */
    *p = 100;  /* OK */
    p = &a;    /* Compiler Error */
    
  • const указатель на const int

    Указатель может указывать только на один int и int не может быть изменен с помощью указателя.

    int a, b;
    const int* const p = &b; /* OK as initialisation, no assignment */
    p = &a;   /* Compiler Error */
    *p = 100; /* Compiler Error */
    

Указатель на указатель

  • Указатель на указатель на int

    Этот код присваивает адрес p1 двойному указателю p (который затем указывает на int* p1 (который указывает на int )).

    Затем p1 на точку int a . Затем изменяется значение a равным 100.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • Указатель на указатель на const int

     void f2(void)
    {
      int b;
      const int *p1;
      const int **p;
      p = &p1; /* OK */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • Указатель на указатель const на int

    void f3(void)
    {
      int b;
      int *p1;
      int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    
  • const указатель на указатель на int

    void f4(void)
    {
      int b;
      int *p1;
      int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* OK */
    }
    
  • Указатель на const указатель на const int

    void f5(void)
    {
      int b;
      const int *p1;
      const int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const указатель на указатель на const int

    void f6(void)
    {
      int b;
      const int *p1;
      const int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const указатель на const указатель на int

    void f7(void)
    {
      int b;
      int *p1;
      int * const * const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’  */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    

Одинаковая звездочка, разные значения

посылка

Самая запутанная вещь, связанная с синтаксисом указателя на C и C ++, состоит в том, что на самом деле существуют два разных значения, которые применяются, когда символ указателя, звездочка ( * ) используется с переменной.

пример

Во-первых, вы используете * для объявления переменной указателя.

int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */

Когда вы не объявляете (или не умножаете), * используется для разыменования переменной указателя:

*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */

Если вы хотите, чтобы существующая переменная указателя удерживала адрес другой переменной, вы не используете * , но делаете это следующим образом:

p = &another_variable;

Обычная путаница среди новичков C-программирования возникает, когда они объявляют и инициализируют переменную указателя одновременно.

int *p = &i;

Поскольку int i = 5; и int i; i = 5; дают тот же результат, некоторые из них могли бы считать int *p = &i; и int *p; *p = &i; дайте тот же результат тоже. Дело в том, что нет, int *p; *p = &i; будет пытаться уважать неинициализированный указатель, который приведет к UB. Никогда не используйте * если вы не декларируете и не разыскиваете указатель.

Заключение

Звездочка ( * ) имеет два различных значения внутри C относительно указателей, в зависимости от того, где они используются. При использовании в объявлении переменной значение в правой части равенства равно значению указателя на адрес в памяти. При использовании с уже объявленной переменной , звездочка будет разыменовывать значение указателя, следуя за ним в указанное место в памяти и позволяя присвоить или получить значение, которое будет там сохранено.

навынос

Важно помнить о своих P и Q, так сказать, при работе с указателями. Помните, когда вы используете звездочку, и что это означает, когда вы ее используете. Осмотр этой крошечной детали может привести к ошибкам и / или неопределенному поведению, с которыми вы действительно не хотите иметь дело.

Указатель на указатель

В C указатель может ссылаться на другой указатель.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &pA;
  int*** pppA = &ppA;

  printf("%d", ***pppA); /* prints 42 */

  return EXIT_SUCCESS;
}

Но ссылки и ссылки напрямую не разрешены.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &&A; /* Compilation error here! */
  int*** pppA = &&&A;  /* Compilation error here! */

  ...

Вступление

Указатель объявляется так же, как любая другая переменная, за исключением того, что звездочка ( * ) помещается между типом и именем переменной, чтобы обозначить это указатель.

int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */

Чтобы объявить две переменные указателя того же типа, в том же объявлении, используйте символ звездочки перед каждым идентификатором. Например,

int *iptr1, *iptr2;
int *iptr3,  iptr4;  /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */

Оператор адреса или ссылки, обозначенный амперсандом ( & ), дает адрес данной переменной, который может быть помещен в указатель соответствующего типа.

int value = 1;
pointer = &value;

Оператор косвенности или разыменования, обозначенный звездочкой ( * ), получает содержимое объекта, на который указывает указатель.

printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */

Если указатель указывает на структуру или тип объединения, вы можете разыменовать его и получить доступ к его членам напрямую с помощью оператора -> :

SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */

В C указатель представляет собой отдельный тип значения, который может быть переназначен и в противном случае рассматривается как переменная в своем собственном праве. Например, следующий пример печатает значение самого указателя (переменной).

printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */

Поскольку указатель является изменяемой переменной, он может не указывать на действительный объект, либо путем установки значения null

pointer = 0;     /* or alternatively */
pointer = NULL;

или просто путем размещения произвольного битового шаблона, который не является допустимым адресом. Последнее очень плохое, потому что он не может быть протестирован до того, как указатель будет разыменован, есть только тест для случая, когда указатель имеет значение null:

if (!pointer) exit(EXIT_FAILURE);

Указатель может быть разыменован только если он указывает на действительный объект, иначе поведение не определено. Многие современные реализации могут помочь вам, подняв некоторую ошибку, такую ​​как ошибка сегментации и прекратить выполнение, но другие могут просто оставить вашу программу в недопустимом состоянии.

Значение, возвращаемое оператором разыменования, является изменчивым псевдонимом исходной переменной, поэтому его можно изменить, изменив исходную переменную.

*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */

Указатели также переустанавливаются. Это означает, что указатель, указывающий на объект, может впоследствии использоваться для указания на другой объект того же типа.

int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */

Как и любая другая переменная, указатели имеют определенный тип. Например, вы не можете назначить адрес short int указателю на long int . Такое поведение упоминается как тип punning и запрещено в C, хотя есть несколько исключений.

Хотя указатель должен иметь определенный тип, память, выделенная для каждого типа указателя, равна памяти, используемой средой для хранения адресов, а не размеру указанного типа.

#include <stdio.h>

int main(void) {
    printf("Size of int pointer: %zu\n", sizeof (int*));      /* size 4 bytes */
    printf("Size of int variable: %zu\n", sizeof (int));      /* size 4 bytes */
    printf("Size of char pointer: %zu\n", sizeof (char*));    /* size 4 bytes */
    printf("Size of char variable: %zu\n", sizeof (char));    /* size 1 bytes */
    printf("Size of short pointer: %zu\n", sizeof (short*));  /* size 4 bytes */
    printf("Size of short variable: %zu\n", sizeof (short));  /* size 2 bytes */
    return 0;
}

(NB: если вы используете Microsoft Visual Studio, которая не поддерживает стандарты C99 или C11, вы должны использовать %Iu 1 вместо %zu в приведенном выше примере.)

Обратите внимание, что приведенные выше результаты могут варьироваться в зависимости от окружения и окружающей среды, но все среды отображают одинаковые размеры для разных типов указателей.

Экстракт на основе информации из Кардиффского университета C Указатели Введение

Указатели и массивы

Указатели и массивы тесно связаны в C. Массивы в C всегда хранятся в смежных местах в памяти. Арифметика указателя всегда масштабируется по размеру указанного пункта. Поэтому, если у нас есть массив из трех двойников и указатель на базу, *ptr относится к первому двойнику *(ptr + 1) ко второму, *(ptr + 2) к третьему. Более удобная нотация - использовать нотацию массива [] .

double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;

/* prints x 0.0, y 1.0 z 2.0 */ 
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);

Таким образом, ptr и имя массива являются взаимозаменяемыми. Это правило также означает, что массив переходит в указатель при передаче в подпрограмму.

double point[3] = {0.0, 1.0, 2.0};

printf("length of point is %s\n", length(point));

/* get the distance of a 3D point from the origin */ 
double length(double *pt)
{
   return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}

Указатель может указывать на любой элемент в массиве или на элемент за последним элементом. Однако ошибка заключается в установке указателя на любое другое значение, включая элемент перед массивом. (Причина в том, что на сегментированных архитектурах адрес перед первым элементом может пересекать границу сегмента, компилятор гарантирует, что это не произойдет для последнего элемента плюс один).


Сноска 1: информация о формате Microsoft может быть найдена с помощью printf() и синтаксиса спецификации формата .

Полиморфное поведение с указателями void

Стандартная библиотечная функция qsort() является хорошим примером того, как можно использовать указатели void, чтобы одна функция работала на множестве разных типов.

void qsort (
    void *base,                                 /* Array to be sorted */
    size_t num,                                 /* Number of elements in array */
    size_t size,                                /* Size in bytes of each element */
    int (*compar)(const void *, const void *)); /* Comparison function for two elements */

Массив, который нужно отсортировать, передается как указатель на void, поэтому можно использовать массив любого типа элемента. Следующие два аргумента qsort() сколько элементов он должен ожидать в массиве, и насколько велики в байтах каждый элемент.

Последний аргумент - это указатель на функцию сравнения, которая сама принимает два указателя void. Предоставляя вызывающей стороне эту функцию, qsort() может эффективно сортировать элементы любого типа.

Вот пример такой функции сравнения, для сравнения поплавков. Обратите внимание, что любая функция сравнения, переданная в qsort() должна иметь подпись этого типа. Способ, которым он сделан полиморфным, заключается в том, чтобы отличить аргументы указателя void от указателей типа элемента, который мы хотим сравнить.

int compare_floats(const void *a, const void *b)
{
    float fa = *((float *)a);
    float fb = *((float *)b);
    if (fa < fb)
        return -1;
    if (fa > fb)
        return 1;
    return 0;
}

Поскольку мы знаем, что qsort будет использовать эту функцию для сравнения float, мы передаем аргументы указателя void обратно в указатели float перед их разыменованием.

Теперь использование полиморфной функции qsort в массиве «array» с длиной «len» очень просто:

qsort(array, len, sizeof(array[0]), compare_floats);


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow