p - указатель на структуру, что делают все эти фрагменты кода? - PullRequest
0 голосов
/ 12 декабря 2018
++p->i 
p++->i
*p->i
*p->i++
(*p->i)++
*p++->i

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

#include <stdio.h>

struct my_structure {
    int i;
};

void main() {
    struct my_structure variable = {20};
    struct my_structure *p = &variable;

    printf("NAME: %d\n", ++p->i);
    printf("NUMBER: %d\n", p++->i);
    printf("RANK: %d", *p->i++);
    printf("name: %d\n", *p->i++);
    printf("number: %d\n", (*p->i)++);
    printf("rank: %d", *p++->i);
}

Вот что я получил после того, как прокомментировал последние четыре print заявления:

NAME: 21
NUMBER: 21

И после раскомментирования кода и компиляции я получаю:

test.c: In function 'main':
test.c:14:24: error: invalid type argument of unary '*' (have 'int')
printf("RANK: %d", *p->i++);
                    ^~~~~~~
test.c:15:26: error: invalid type argument of unary '*' (have 'int')
printf("name: %d\n", *p->i++);
                      ^~~~~~~
test.c:16:29: error: invalid type argument of unary '*' (have 'int')
printf("number: %d\n", (*p->i)++);

                         ^~~~~
test.c:17:24: error: invalid type argument of unary '*' (have 'int')
printf("rank: %d", *p++->i);

Ответы [ 4 ]

0 голосов
/ 12 декабря 2018

Во-первых, некоторые напоминания:

p++ соответствует текущему значению p, и как побочный эффект добавляет 1 к p.Если p является указателем, он устанавливается для указания на следующий объект в последовательности.

++p соответствует текущему значению p + 1, и в качестве побочного эффекта добавляет 1 к p.Опять же, если p является указателем, он устанавливается для указания на следующий объект в последовательности.

Форма постфикса ++ и оператор -> имеют одинаковый приоритет, и они имеют более высокий приоритет, чем унарная (префиксная) форма ++ и *.Таким образом, выражение вроде ++p->i анализируется как ++(p->i), p->i++ анализируется как (p->i)++, *p->i анализируется как *(p->i) и т. Д.

С этим вне пути...

Выражение

++p->i

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

++(p->i)

и оценивается как текущее значение p->i плюс 1 и как побочный эффект обновляет p->i.

Выражение

p++->i

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

(p++)->i

и оценивается как текущий p->i, затем обновляетсяp для указания на следующий struct объект в последовательности.

Выражение

*p->i

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

*(p->i)

, поскольку -> имеетболее высокий приоритет, чем унарный *.Операнд унарного * должен иметь тип указателя, но p->i является целым числом, поэтому компилятор сгенерирует это выражение.

Выражение

*p->i++

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

*((p->i)++)

Опять же, компилятор выдаст это выражение, поскольку операнд * не является указателем.

Выражение

(*p->i)++

парсится как

(*(p->i))++

Опять же, p->i не имеет типа указателя, поэтому компилятор выдаст yak. ​​

Выражение

*p++->i

проанализировано как

*((p++)->i)

и, опять же, больше яккаге.

0 голосов
/ 12 декабря 2018

Давайте разберем это по оператору.

x->y: Это разыменовывает указатель на структуру (x) и затем обращается к указанной переменной-члену (y).Это имеет смысл, только если x является указателем на структуру.Это также эквивалентно (*x).y.

++x: предварительное увеличение x.Это увеличивает значение x на 1, а затем возвращает НОВОЕ значение x.Он имеет более низкий приоритет, чем оператор ->, поэтому ++p->i получит i, как указано выше, а затем увеличит его.

x++: постинкремент x.Это увеличивает значение x на 1, а затем возвращает значение OLD x.Но на этот раз операторы имеют одинаковый приоритет и выполняются слева направо.Затем он будет увеличивать p, а затем разыменовываться там, где раньше был p для доступа к i.Это даст вам значение i, но теперь p указывает на неинициализированную / неизвестную память (если только p не был в массиве, в этом случае он теперь указывает на следующий член этого массива).

*x: Разыменовывает x, как я упоминал выше под ->, но в этом примере мы сейчас делаем это дважды, в результате чего получается эквивалент ((**p).i)++).Однако, поскольку p указывает на структуру, а не указывает на указатель на структуру, это ошибка компилятора.Это относится и к следующему выражению, поскольку в нем просто явно прописан тот же порядок операций, которому компилятор уже будет соответствовать.

Собрав все это вместе, мы придем к последнему, что будет по порядку:

  1. Разыменование p.(пока все хорошо)
  2. Увеличивайте результат.Но приращение не определено для структуры, поэтому ошибка компилятора.
  3. Разыменование этого результата.Опять же, больше не указатель, поэтому мы не можем разыменовать.Ошибка компилятора.
  4. Доступ к элементу i из этого результата.

Здесь вы можете ознакомиться с правилами приоритетов операторов, которые я использовал здесь: https://en.cppreference.com/w/c/language/operator_precedence

0 голосов
/ 12 декабря 2018

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

#include <stdio.h>

struct my_structure
{
    char *i;
};

#define EXPR(x) #x, x

int main(void)
{
    char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
    struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
    struct my_structure *p = variables;

    printf("%10s: %s\n", EXPR(++p->i));
    printf("%10s: %s\n", EXPR(p++->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %d\n", EXPR((*p->i)++));
    printf("%10s: %d\n", EXPR(*p++->i));
    return 0;
}

, который генерирует вывод:

    ++p->i: inter
    p++->i: inter
   *p->i++: 66
   *p->i++: 97
 (*p->i)++: 115
   *p++->i: 116

Макрос EXPR просто позволяет мне не повторять выражения в коде, и все же получить обе строкиform и значение в вызове printf().

Когда все начинается, p->i указывает на строку "Winter".

  • ++p->i: inter - предварительное увеличениеуказатель p->i, поэтому он указывает на i из Winter.
  • p++->i: inter - после увеличения указатель p (для указания на "Bash"), но результатТо же, что и раньше, поскольку приращение вступает в силу после использования p->i.
  • *p->i++: 66 - после увеличения указатель p->i (таким образом, он указывает на a в Bash) и сообщаетзначение, на которое указывает до приращения, которое составляет B (66 в ASCII).
  • *p->i++: 97 - то же выражение, но указатель указывает на a (97) перед приращением и на s после увеличения.
  • (*p->i)++: 115 - после увеличения буквы, на которую указывает p->i, сообщая s, но изменяявведите t.
  • *p++->i: 116 - постинкремент p, чтобы он указывал на строку «In», сообщая при этом t (116).

Вот альтернатива с большим количеством инструментов:

#include <stdio.h>

struct my_structure
{
    char *i;
};

#define EXPR(x) #x, x

int main(void)
{
    char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
    struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
    struct my_structure *p = variables;

    for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
        printf("strings[%zu] = [%s]\n", i, strings[i]);

    for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
        printf("variables[%zu].i = [%s]\n", i, variables[i].i);

    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %s\n", EXPR(++p->i));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %s\n", EXPR(p++->i));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR((*p->i)++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p++->i));
    printf("%10s: %s\n", EXPR(p->i));

    for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
        printf("strings[%zu] = [%s]\n", i, strings[i]);

    for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
        printf("variables[%zu].i = [%s]\n", i, variables[i].i);

    return 0;
}

и его вывод:

strings[0] = [Winter]
strings[1] = [Bash]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [Winter]
variables[1].i = [Bash]
variables[2].i = [Is]
variables[3].i = [Here]
      p->i: Winter
    ++p->i: inter
      p->i: inter
    p++->i: inter
      p->i: Bash
   *p->i++: 66
      p->i: ash
   *p->i++: 97
      p->i: sh
 (*p->i)++: 115
      p->i: th
   *p++->i: 116
      p->i: Is
strings[0] = [Winter]
strings[1] = [Bath]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [inter]
variables[1].i = [th]
variables[2].i = [Is]
variables[3].i = [Here]

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

0 голосов
/ 12 декабря 2018

См. Комментарии в строке:

struct my_structure {
    int i;
};

void main() {
    struct my_structure variable = {20};
    struct my_structure *p = &variable;

    printf("NAME: %d\n", ++p->i);     //pre-increments i by 1 (prints 21)
    printf("NUMBER: %d\n", (p++)->i);   //changes location pointed to. (meaningless, ub, printed 3 for me)
    //printf("RANK: %d", *p->i++);      // error, (will not be included in build)
    //printf("name: %d\n", *p->i++);      // error, (will not be included in build)
    //printf("number: %d\n", (*p->i)++);// error, (will not be included in build)
    //printf("rank: %d", *p++->i);    // error, (will not be included in build)

    getchar();
}

Хотя и первое, и второе операторы синтаксически верны (т. Е. Компиляция, сборка и запуск без указания проблемы), только первое имеет смысл, хотя для чегоцель, я не знаю.Учитывая объявление: struct my_structure variable = {20};, определяет только одну ячейку памяти, второе приращение указателя помещает его местоположение за пределы вашего определения и указывает на неизвестное значение.В моем случае это указывало на 3, но могло содержать что угодно.И, поскольку он не принадлежит, вызывает неопределенное поведение .Вот почему весьма вероятно, что запуск полученного исполняемого файла на разных компьютерах или даже на одном и том же компьютере в разное время приведет к различным результатам.

...