Вопрос

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

#include <stdio.h>

void add_one_v1(int *our_var_ptr)
{
    *our_var_ptr = *our_var_ptr +1;
}

void add_one_v2(int *our_var_ptr)
{
    *our_var_ptr++;
}

int main()
{
    int testvar;

    testvar = 63;
    add_one_v1(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints out 64                     */
    printf("@ %p\n\n", &(testvar));

    testvar = 63;
    add_one_v2(&(testvar));         /* Try first version of the function */
    printf("%d\n", testvar);        /* Prints 63 ?                       */
    printf("@ %p\n", &(testvar));   /* Address remains identical         */
}

Выход:

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0

Что именно делает *our_var_ptr++ оператор во второй функции (add_one_v2) делать, так как это явно не то же самое, что *our_var_ptr = *our_var_ptr +1?

Это было полезно?

Решение

Из-за правил приоритета операторов и того факта, что ++ является постфиксным оператором, add_one_v2() разыменовывает указатель, но ++ на самом деле применяется к самому указателю.Однако помните, что C всегда использует передачу по значению: add_one_v2() увеличивает свою локальная копия указателя, что не окажет никакого влияния на значение, хранящееся по этому адресу.

В качестве проверки замените add_one_v2() с этими фрагментами кода и посмотрите, как это повлияет на вывод:

void add_one_v2(int *our_var_ptr)
{
    (*our_var_ptr)++;  // Now stores 64
}

void add_one_v2(int *our_var_ptr)
{
    *(our_var_ptr++);  // Increments the pointer, but this is a local
                       // copy of the pointer, so it doesn't do anything.
}

Другие советы

Это одна из тех маленьких ошибок, которые делают C и C++ такими интересными.Если вы хотите согнуть свой мозг, разберитесь в этом:

while (*dst++ = *src++) ;

Это строковая копия.Указатели продолжают увеличиваться до тех пор, пока не будет скопирован символ со значением 0.Как только вы поймете, почему этот трюк работает, вы никогда больше не забудете, как ++ работает с указателями.

P.S.Вы всегда можете изменить порядок операторов с помощью круглых скобок.Следующее будет увеличивать значение, на которое указывает, а не сам указатель:

(*our_var_ptr)++;

ХОРОШО,

*our_var_ptr++;

это работает следующим образом:

  1. Разыменование происходит первым, предоставляя вам ячейку памяти, указанную our_var_ptr (который содержит 63).
  2. Затем выражение оценивается, результат 63 по-прежнему равен 63.
  3. Результат выбрасывается (вы ничего с ним не делаете).
  4. our_var_ptr затем увеличивается ПОСЛЕ оценки.Меняется то, куда указывает указатель, а не то, на что он указывает.

Фактически это то же самое, что и следующее:

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

Имеет смысл?В ответе Марка Рэнсома есть хороший пример этого, за исключением того, что он фактически использует результат.

Здесь много путаницы, поэтому вот модифицированная тестовая программа, чтобы прояснить происходящее (или, по крайней мере, прояснить ситуацию).эээ):

#include <stdio.h>

void add_one_v1(int *p){
  printf("v1: pre:   p = %p\n",p);
  printf("v1: pre:  *p = %d\n",*p);
    *p = *p + 1;
  printf("v1: post:  p = %p\n",p);
  printf("v1: post: *p = %d\n",*p);
}

void add_one_v2(int *p)
{
  printf("v2: pre:   p = %p\n",p);
  printf("v2: pre:  *p = %d\n",*p);
    int q = *p++;
  printf("v2: post:   p = %p\n",p);
  printf("v2: post:  *p = %d\n",*p);
  printf("v2: post:   q = %d\n",q);
}

int main()
{
  int ary[2] = {63, -63};
  int *ptr = ary;

    add_one_v1(ptr);         
    printf("@ %p\n", ptr);
    printf("%d\n", *(ptr));  
    printf("%d\n\n", *(ptr+1)); 

    add_one_v2(ptr);
    printf("@ %p\n", ptr);
    printf("%d\n", *ptr);
    printf("%d\n", *(ptr+1)); 
}

с полученным результатом:

v1: pre:   p = 0xbfffecb4
v1: pre:  *p = 63
v1: post:  p = 0xbfffecb4
v1: post: *p = 64
@ 0xbfffecb4
64
-63

v2: pre:   p = 0xbfffecb4
v2: pre:  *p = 64
v2: post:  p = 0xbfffecb8
v2: post: *p = -63
v2: post:  q = 64

@ 0xbfffecb4
64
-63

Четыре вещи, на которые следует обратить внимание:

  1. изменения в локальной копии указателя нет отражено в вызывающем указателе.
  2. изменения цели локального указателя влияют на цель вызывающего указателя (по крайней мере, до тех пор, пока целевой указатель не будет обновлен)
  3. значение, указанное в add_one_v2 является нет увеличивается, и ни одно из следующих значений не является, но указатель
  4. приращение указателя в add_one_v2 случается после разыменование

Почему?

  • Потому что ++ связывает сильнее, чем * (как разыменование или умножение), поэтому приращение add_one_v2 применяется к указателю, а не к тому, на что он указывает.
  • почта приращения происходят после оценка термина, поэтому при разыменовании получает первое значение в массиве (элемент 0).

Как указывали другие, приоритет операторов приводит к тому, что выражение в функции v2 рассматривается как *(our_var_ptr++).

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

  1. Сохраните текущее значение указателя
  2. Увеличение указателя
  3. Разыменуйте значение указателя, сохраненное на шаге 1.

Как объяснил htw, вы не видите изменения значения указателя, поскольку оно передается по значению в функцию.

Our_var_ptr — это указатель на некоторую память.т.е. это ячейка памяти, в которой хранятся данные.(в данном случае 4 байта в двоичном формате int).

*our_var_ptr — это разыменованный указатель — он идет в то место, на которое «указывает» указатель.

++ увеличивает значение.

так. *our_var_ptr = *our_var_ptr+1 разыменовывает указатель и добавляет единицу к значению в этом месте.

Теперь добавьте приоритет оператора – прочитайте его как (*our_var_ptr) = (*our_var_ptr)+1 и вы видите, что сначала происходит разыменование, поэтому вы берете значение и увеличиваете его.

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

Мой совет: при использовании разыменования (или чего-либо еще) используйте скобки, чтобы сделать ваше решение явным.Не пытайтесь запомнить правила приоритета, поскольку однажды вы будете использовать другой язык, в котором они немного отличаются, и вы запутаетесь.Или старый и в конечном итоге забывает, что имеет более высокий приоритет (как я делаю с * и ->).

Если вы не используете круглые скобки для указания порядка операций, префиксные и постфиксные приращения имеют приоритет над ссылкой и разыменованием.Однако приращение префикса и приращение постфикса — это разные операции.В ++x оператор берет ссылку на вашу переменную, добавляет к ней ее и возвращает по значению.В x++ оператор увеличивает вашу переменную, но возвращает ее старое значение.Они ведут себя примерно так (представьте, что они объявлены как методы внутри вашего класса):

//prefix increment (++x)
auto operator++()
{
    (*this) = (*this) + 1;
    return (*this);
}

//postfix increment (x++)
auto operator++(int) //unfortunatelly, the "int" is how they differentiate
{
    auto temp = (*this);
    (*this) = (*this) + 1; //same as ++(*this);
    return temp;
}

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

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

Вот пример:

char * x = {'a', 'c'};
char   y = *x++; //same as *(x++);
char   z = *x;

Во второй строке указатель x будет увеличен до разыменования, но разыменование произойдет по старому значению x (которое является адрес возвращается постфиксным приращением).Таким образом, y будет инициализирован буквой «a», а z — буквой «c».Но если вы сделаете это так:

char * x = {'a', 'c'};
char   y = (*x)++;
char   z = *x;

Здесь x будет разыменован, а ценить указанное им значение («a») будет увеличено (до «b»).Поскольку приращение постфикса возвращает старое значение, y по-прежнему будет инициализироваться с помощью 'a'.А поскольку указатель не изменился, z будет инициализирован новым значением «b».

Теперь давайте проверим случаи префиксов:

char * x = {'a', 'c'};
char   y = *++x; //same as *(++x)
char   z = *x;

Здесь разыменование произойдет по увеличенному значению x (которое немедленно возвращается оператором приращения префикса), поэтому и y, и z будут инициализированы с помощью 'c'.Чтобы получить другое поведение, вы можете изменить порядок операторов:

char * x = {'a', 'c'};
char   y = ++*x; //same as ++(*x)
char   z = *x;

Здесь вы гарантируете, что сначала увеличиваете содержимое x, а значение указателя никогда не меняется, поэтому y и z будут присвоены 'b'.в стркпи функции (упомянутой в другом ответе), приращение также выполняется первым:

char * strcpy(char * dst, char * src)
{
    char * aux = dst;
    while(*dst++ = *src++);
    return aux;
}

На каждой итерации первым обрабатывается src++, который, будучи постфиксным приращением, возвращает старое значение src.Затем старое значение src (которое является указателем) разыменовывается и присваивается всему, что находится в левой части оператора присваивания.Затем значение dst увеличивается, а его старое значение разыменовывается, чтобы стать значением lvalue и получить старое значение src.Вот почему dst[0] = src[0], dst[1] = src[1] и т. д., вплоть до тех пор, пока *dst не будет присвоено значение 0, разрывая цикл.

Приложение:

Весь код в этом ответе был протестирован на языке C.В C++ вы, вероятно, не сможете инициализировать указатель списком.Итак, если вы хотите протестировать примеры на C++, вам следует сначала инициализировать массив, а затем преобразовать его в указатель:

char w[] = {'a', 'c'};
char * x = w;
char   y = *x++; //or the other cases
char   z = *x;

Оператор «++» имеет более высокий приоритет по сравнению с оператором «*», что означает, что адрес указателя будет увеличен перед разыменованием.

Однако оператор «+» имеет более низкий приоритет, чем оператор «*».

Попробую ответить на этот вопрос немного с другой стороны...Шаг 1 Давайте посмотрим на операторов и операндов:В данном случае это операнд, и у вас есть два оператора: * для разыменования и ++ для увеличения.Шаг 2, который имеет более высокий приоритет ++, имеет более высокий приоритет по сравнению с * Шагом 3, где есть ++, это справа, что означает ПОЧТА Приращение в этом случае компилятор принимает «умственную заметку», чтобы выполнить приращение ПОСЛЕ это делается со всеми остальными операторами...Обратите внимание, что если это было *++ P, то это сделает это раньше, в данном случае, это так же эквивалентно получению двух из регистра процессора, один будет содержать значение Dereferenced *P, а другой будет содержать значение Увеличение P ++, причина в этом случае их два, это пост -активность ...Вот здесь в данном случае хитрость, и это выглядит как противоречие.Можно ожидать, что ++ будет иметь приоритет над *, что он и делает, только то, что пост означает, что он будет применяться только после всех других операндов, до следующего ';'; '; жетон ...

    uint32_t* test;
    test = &__STACK_TOP;


    for (i = 0; i < 10; i++) {
        *test++ = 0x5A5A5A5A;
    }

    //same as above

    for (i = 0; i < 10; i++) {
        *test = 0x5A5A5A5A;
        test++;
    }

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

        ((unsigned char*) test)++;

Это увеличит адрес только на 1 байт;)

Из K&R, стр. 105:«Значение *t++ — это символ, на который указывал t до увеличения t».

Картинки стоят примерно тысячи слов (плюс-минус миллион или около того)… а символы могут быть картинками (и наоборот).

Итак, для тех из нас, кто ищет tl;dr's (оптимизированное потребление данных), но при этом требуется кодирование «(в основном) без потерь», векторные изображения/изображения/иллюстрации/демонстрации имеют первостепенное значение.

Другими словами, просто проигнорируйте мои последние два утверждения и посмотрите ниже.

Действительные формы:


*а++ ≣ *(a++)
≣ (a++)[0] ≣ a++[0]
≣ 0[a++] // Don't you dare use this (“educational purposes only”)
// These produce equivalent (side) effects;
≡ val=*a,++a,val
≡ ptr=a,++a,*ptr
≡ *(ptr=a,++a,ptr)

*++а ≣ *(++a)
≣ *(a+=1) ≣ *(a=a+1)
≣ (++a)[0] ≣ (a+=1)[0] ≣ (a=a+1)[0] // ()'s are необходимый
≣ 0[++a] // 0[a+=1], etc... “educational purposes only”
// These produce equivalent (side) effects:
≡ ++a,*a
≡ a+=1,*a
≡ a=a+1,*a

+*а ≣ ++(*a)
≣ *a+=1
≣ *a=*a+1
≣ ++a[0] ≣ ++(a[0])
≣ ++0[a] // STAY AWAY

(*а)++ // Note that this does NOT return a pointer;
// Location `a` points to does not change
// (i.e. the 'value' of `a` is unchanged)
≡ val=*a,++*a,val

Примечания

/* Оператор косвенности/почтения должен пр̲е̲уступить целевой идентификатор:*/
a++*;
a*++;
++a*;

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

void inc_value_and_ptr(int **ptr)
{
   (**ptr)++;
   (*ptr)++;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top