Подтвердить что ты не робот

Что означает получение адреса переменной массива?

Сегодня я прочитал фрагмент C, который меня действительно смущает:

#include <stdio.h>

int
main(void)
{
    int a[] = {0, 1, 2, 3};

    printf("%d\n", *(*(&a + 1) - 1));
    return 0;
}

По-моему, &a + 1 не имеет смысла, но работает без ошибок.

Может кто-нибудь объяснить, что это значит, спасибо. И охватывает ли Библия K & R C?

UPDATE0: Прочитав ответы, я понимаю, что эти два выражения в основном меня смущают:

4b9b3361

Ответ 1

Разрежьте его.

a имеет тип int [4] (массив из 4 int). Его размер составляет 4 * sizeof(int).

&a имеет тип int (*)[4] (указатель на массив из 4 int).

(&a + 1) также имеет тип int (*)[4]. Он указывает на массив из 4 int, который запускает 1 * sizeof(a) bytes (или 4 * sizeof(int) bytes) после начала a.

*(&a + 1) имеет тип int [4] (массив из 4 int). Он начинает 1 * sizeof(a) байты (или 4 * sizeof(int) байты после начала a).

*(&a + 1) - 1 имеет тип int * (указатель на int), потому что массив *(&a + 1) распадается на указатель на его первый элемент в этом выражении. Он укажет на int, который запустит 1 * sizeof(int) байт до начала *(&a + 1). Это то же значение указателя, что и &a[3].

*(*(&a + 1) - 1) имеет тип int. Поскольку *(&a + 1) - 1 - это то же самое значение указателя, что и &a[3], *(*(&a + 1) - 1) эквивалентно a[3], которое было инициализировано на 3, так что это число, напечатанное printf.

Ответ 2

Сначала немного напоминания (или что-то новое, если вы этого не знали раньше): для любого массива или указателя p и index i выражение p[i] точно такое же, как *(p + i).

Теперь, надеюсь, поможет вам понять, что происходит...

Массив a в вашей программе хранится где-то в памяти, точно там, где это не имеет значения. Чтобы получить местоположение, где хранится a, т.е. Получить указатель на a, вы используете адрес-оператора & как &a. Важно узнать здесь, что указатель сам по себе не означает ничего особенного, главное - базовый тип указателя. Тип a равен int[4], т.е. a представляет собой массив из четырех элементов int. Тип выражения &a является указателем на массив из четырех int или int (*)[4]. Скобки важны, потому что тип int *[4] представляет собой массив из четырех указателей на int, что совсем другое.

Теперь, чтобы вернуться к исходной точке, p[i] совпадает с *(p + i). Вместо p имеем &a, поэтому наше выражение *(&a + 1) совпадает с (&a)[1].

Теперь, что объясняет, что означает *(&a + 1) и что он делает. Теперь давайте немного подумаем о макете памяти массива a. В памяти это выглядит как

+---+---+---+---+
| 0 | 1 | 2 | 3 |
+---+---+---+---+
^
|
&a

Выражение (&a)[1] рассматривает &a, поскольку это массив массивов, которого он определенно не является, и доступ ко второму элементу в этом массиве, который будет за пределами границ. Это, конечно, технически - это поведение undefined. Давайте продолжим с ним на мгновение, и рассмотрим, как это будет выглядеть в памяти:

+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | . | . | . | . |
+---+---+---+---+---+---+---+---+
^               ^
|               |
(&a)[0]         (&a)[1]

Теперь помните, что тип a (который совпадает с (&a)[0] и поэтому означает, что (&a)[1] также должен быть этим типом) - это массив из четырех int. Поскольку массивы естественным образом распадаются на указатели на первый элемент, выражение (&a)[1] совпадает с &(&a)[1][0], а его тип - указателем на int. Поэтому, когда мы используем (&a)[1] в выражении, которое дает нам компилятор, это указатель на первый элемент во втором (несуществующем) массиве &a. И снова мы приходим к уравнению p[i] equals *(p + i): (&a)[1] является указателем на int, it p в выражении *(p + i), поэтому полное выражение *((&a)[1] - 1) и смотрит на макет памяти выше вычитания одного int из указателя, заданного (&a)[1], дает нам элемент до (&a)[1], который является последним элементом в (&a)[0], то есть он дает нам (&a)[0][3], который совпадает с a[3].

Итак, выражение *(*(&a + 1) - 1) совпадает с выражением a[3].

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

Ответ 3

&a + 1 будет указывать на память сразу после последнего элемента a или лучше сказать после массива a, так как &a имеет тип int (*)[4] (указатель на массив из четырех int 's), Построение такого указателя допускается стандартным, но не разыменованием. В результате вы можете использовать его для последующей арифметики.

Итак, результат *(&a + 1) равен undefined. Но тем не менее *(*(&a + 1) - 1) является чем-то более интересным. Эффективно оценивается последний элемент в a. Подробное объяснение см. В fooobar.com/questions/211893/.... И просто замечание - этот хак может быть заменен более понятной и понятной конструкцией: a[sizeof a / sizeof a[0] - 1] (конечно, он должен применяться только к массивам, а не к указателям).

Ответ 4

Лучше всего доказать это себе:

$ cat main.c
#include <stdio.h>
main()
{
  int a[4];
  printf("a    %p\n",a);
  printf("&a   %p\n",&a);
  printf("a+1  %p\n",a+1);
  printf("&a+1 %p\n",&a+1);
}

И вот адреса:

$ ./main
a    0x7fff81a44600 
&a   0x7fff81a44600 
a+1  0x7fff81a44604
&a+1 0x7fff81a44610

Первые 2 - это один и тот же адрес. Третий 4 больше (это sizeof(int)). Четвертый - 0x10 = 16 больше (это sizeof(a))

Ответ 5

Если у вас есть объект типа T, например

T obj;

то объявление

T *p = &obj;

инициализирует указатель p адресом памяти, занимаемой объектом obj

Выражение p + 1 указывает на память после объекта obj. Значение выражения p + 1 равно значению &obj plus sizeof( obj ), эквивалентному

( T * )( ( char * )&obj + sizeof( obj ) )

Итак, если у вас есть массив, указанный в вашем сообщении int a[] = {0, 1, 2, 3};, вы можете переписать его объявление с помощью typedef следующим образом:

typedef int T[4];

T a = { 0, 1, 2, 3 };

sizeof( T ) в этом случае равен sizeof( int[4] ) и, в свою очередь, равен 4 * sizeof( int )

Выражение &a дает адрес объема памяти, занимаемого массивом. Выражение &a + 1 дает адрес памяти после массива, а значение выражения равно &a + sizeof( int[4] )

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

Таким образом, выражение &a + 1 указывает на воображаемый элемент типа int[4] после реального первого элемента a. Выражение *(&a + 1) дает этот воображаемый элемент. Но поскольку этот элемент представляет собой массив с типом int[4], тогда это выражение преобразуется в указатель на его первый элемент типа int *

Этот первый элемент следует за последним элементом массива a. И в этом случае выражение *(&a + 1) - 1 дает адрес этого последнего элемента массива a

В результате разыменования в *(*(&a + 1) - 1) вы получите значение последнего элемента массива a, поэтому будет выведено число 3.

Ответ 6

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

printf("%d\n", (&a)[1][-1]);

В этом случае, на мой взгляд, более явное, что происходит:

указывается указатель на массив a

  • указатель используется так, как если бы он был массивом: массив таких элементов, как a, то есть массивы из 4 целых чисел, используется 1-й элемент этого массива.

  • Так как a на самом деле не массив, а только один элемент (состоящий из четыре подэлемента!) это индексирует кусок памяти непосредственно после

  • [-1] читает целое число непосредственно перед памятью непосредственно после a, который является последним подэлементом

Ответ 7

*(*(&a + 1) - 1)

является неудобным и опасным способом обращения к последнему элементу массива. & a - адрес массива типа int [4]. (& a + 1) дает следующий массив int [4] после текущего адресата a. Разрушив его, используя * (& a + 1), вы делаете его в * int, а с добавлением -1 вы указываете на последний элемент a. Затем этот последний элемент разыменовывается и, следовательно, возвращается значение 3 (в вашем примере).

Это хорошо работает, если тип элементов массива имеет ту же длину, что и выравнивание целевого ЦП. Рассмотрим случай, когда у вас есть массив типа uint8 и длина 5:   uint8 ar [] = {1,2,3,4,5}; Если вы сделаете то же самое сейчас (в 32-битной архитектуре), вы будете обращаться к unsed дополняющему байту после значения 5. Таким образом, ar [5] имеет адрес, который выровнен до 4 байтов. Отдельные элементы ar сравниваются с одиночными байтами. То есть, адрес ar [0] совпадает с адресом ar, адрес ar [1] является одним байтом после ar (а не 4 байта после ar),..., адресом ar [4 ] ar плюс 5 байтов и, следовательно, не выровнены с 4 байтами. Если вы делаете (& a + 1), вы получаете адрес следующего массива uint8 [5], который выровнен до 4 байтов, т.е. Ar плюс 8 байтов. Если вы берете этот адрес ar плюс 8 байт и переходите один байт назад, вы читаете в ar plus 7, который не используется.