Понимание C-указателей, массивов и отрицательных индексов - PullRequest
0 голосов
/ 25 мая 2018

Я пытаюсь выучить указатели на Си и беру викторину для этой цели.Вот вопрос:

#include <stdio.h>

char *c[] = {"GeksQuiz", "MCQ", "TEST", "QUIZ"};
char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;

int main()
{
    printf("%s ", **++cpp);
    printf("%s ", *--*++cpp+3);
    printf("%s ", *cpp[-2]+3);
    printf("%s ", cpp[-1][-1]+1);
    return 0;
}

Результат строки:

 printf("%s ", *cpp[-2]+3);

смущает меня, но позвольте мне шаг за шагом объяснить, как я это понимаю.

  • char *c[] - это массив указателей на char .
  • char **cp[] - это массив указателей, которые указывают на указатель на char (я считаю,это как оболочка для *c[] в обратном порядке).
  • char ***cpp - это указатель на указатель, который указывает на указатель на char (я считаю это оболочкой для **cp[]выполнять изменения на месте).

**++cpp - поскольку cpp указывает на cp, тогда ++cpp будет указывать на cp+1, что составляет c+2, поэтому двойное разыменование будетprint TEST.

*--*++cpp+3 - поскольку теперь cpp указывает на cp+1, тогда ++cpp будет указывать на cp+2, что составляет c+1, и следующая операция -- будетдайте нам указатель на c, так что при последнем разыменовании будет напечатано sQuiz.

Здесь возникает путаница:

cpp[-2] - так как теперь cpp указывает на cp+2, чтоя могу подтвердитьс помощью

printf("%p\n", cpp); // 0x601090   
printf("%p\n", cp+2); // 0x601090

здесь я печатаю адреса указателей в c

printf("c - %p\n", c); // c - 0x601060
printf("c+1 - %p\n", c+1); // c+1 - 0x601068
printf("c+2 - %p\n", c+2); // c+2 - 0x601070
printf("c+3 - %p\n", c+3); // c+3 - 0x601078

, поэтому при разыменовании, подобном этому *(cpp[0]) или **cpp, я ожидаю получить значение MCQиз c+1

printf("%p\n", &*(cpp[0])); // 0x601068

но когда я скажу *(cpp[-2]), я получу QUIZ, но я бы предпочел получить какое-то мусорное значение.

Итак, мои вопросы:

  1. Как работает магия с *--*++cpp+3, я имею в виду то, что модифицируется частью --, которая позволяет мне получить MCQ вместо TEST при разыменовании, подобном этому **cpp, Я предполагаю, что этот указатель *++cpp+3 сохраняет состояние после применения --, но пока не может представить, как он работает.

  2. Почему следующее работает так, как работает (cpp[-2] часть):

    printf("%p\n", &*cpp[1]); // 0x601060 -> c
    printf("%p\n", &*(cpp[0])); // 0x601068 -> c+1
    printf("%p\n", &*(cpp[-1])); // 0x601070 -> c+2
    printf("%p", &*(cpp[-2])); // 0x601078 -> c+3
    

Кажется, что он имеет обратный порядок, я могу принять &*(cpp[0]), указывая на c+1, но я ожидал бы &*cpp[1]укажите c+2 и &*(cpp[-1]) до c.Что я нашел в этом вопросе: Разрешены ли отрицательные индексы массива в C?

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

1 Ответ

0 голосов
/ 25 мая 2018

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

, когда я скажу *(cpp[-2]), я получу QUIZ, но я бы предпочел ожидатьчтобы получить некоторое значение мусора.

Отрицательные значения в порядке. Обратите внимание на следующее :

По определению индексный оператор E1[E2] точно идентичен *((E1)+(E2)).

Зная это, так как cpp == cp+2, затем:

cpp[-2] == *(cpp-2) == *(cp+2-2) == *cp == c+3

И, следовательно:

*cpp[-2]+3 == *(c+3)+3 == c[3]+3

Что означает адрес "QUIZ" плюс 3 позиции указателя char, поэтому вы переходите к printf адрес символа Z в "QUIZ", что означает, что он начнет печатать строку оттуда.

На самом деле, если вам интересно, -2[cpp] также эквивалентно и допустимо.


Теперь вопросы:

  1. Как работает магия с *--*++cpp+3, я имею в виду то, что модифицируется частью -, которая позволяет мне получить MCQ вместо TEST когда я разыскиваю таким образом **cpp, я предполагаю, что этот указатель *++cpp+3 сохраняет состояние после применения -, но пока не может представить, как он работает.

Давайте разберемся с этим (напомним, что cpp == cp+1 здесь, как вы правильно указали):

    ++cpp   // cpp+1 == cp+2 (and saving this new value in cpp)
   *++cpp   // *(cp+2) == cp[2]
 --*++cpp   // cp[2]-1 == c (and saving this new value in cp[2])
*--*++cpp   // *c
*--*++cpp+3 // *c+3

И это указывает на sQuiz как вы правильно указали.Однако cpp и cp[2] были изменены, поэтому теперь у вас есть:

cp[] == {c+3, c+2, c, c}
cpp  == cp+2

Факт изменения cp[2] не используется в остальной части вопроса, но важно отметить -- особенно если вы напечатали значения указателей.Смотри:

Почему следующее работает так, как оно работает (часть cpp[-2]):

printf("%p\n", &*cpp[1]); // 0x601060 -> c
printf("%p\n", &*(cpp[0])); // 0x601068 -> c+1
printf("%p\n", &*(cpp[-1])); // 0x601070 -> c+2
printf("%p", &*(cpp[-2])); // 0x601078 -> c+3

Сначала давайте упростим &*x до x.Затем, выполнив что-то похожее, как указано выше, если cpp == cp+2 (как указано выше), вы увидите, что:

cpp[ 1] == cp[3] == c
cpp[ 0] == cp[2] == c   // Note this is different to what you had
cpp[-1] == cp[1] == c+2
cpp[-2] == cp[0] == c+3
Я, очевидно, путаю многие вещи и могу назвать что-то указателем, который в действительности не один, я хотел бы понять концепцию указателя, поэтому буду рад, если кто-то покажет мне, где я неправ.

На самом деле вы получили это довольно хорошо!: -)

По сути, указатель - это целое число, представляющее адрес памяти.Однако, когда вы выполняете арифметику с ним, он учитывает размер типа, на который он указывает.Вот почему, если c == 0x601060 и sizeof(char*) == 8, то:

c+1 == 0x601060 + 1*sizeof(char*) == 0x601068 // Instead of 0x601061
c+2 == 0x601060 + 2*sizeof(char*) == 0x601070 // Instead of 0x601062
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...