Как определяется арифметика с массивом &


Я получаю большую часть арифметики указателей, пока не увидел следующее:

int x[5];
sizeof(x)  // equals 20
sizeof(&x) // equals 4 -- sizeof(int))

До сих пор я придаю этому смысловое значение: указатель на N-элементный массив T - в случае &x

Однако, когда мы делаем x+1, мы увеличиваем с sizeof(int), а когда мы делаем &x+1, мы увеличиваем с sizeof(x).

Есть ли какая-то лежащая в основе этого логика, то есть некоторые эквивалентности, потому что это кажется очень неинтуитивным.

/ edit, благодаря @WhozCraig я пришел к выводу, что сделал Ошибка:

sizeof(&x) // equals 4 -- sizeof(int)) 

Должно быть

sizeof(&x) // equals 8 -- sizeof(int))

Урок усвоен: не отправляйте код, который вы не запускали напрямую

4 2

4 ответа:

Называется типизированного указателя математика (или типизированный указатель-арифметика), и интуитивно понятен, когда вы получаете одно засело в вашей ДНК: указатель математика регулирует адресов на основе типа указателя, который содержит Саид-адрес.

В вашем примере, что такое тип из x? Это массив int. но каков тип выражения x? Хммм. Согласно стандарту, значение выражения x является адресом первого элемента массива, а тип является типом указателя на элемент, в данном случае указателем на - int.

Тот же стандарт диктует, что для любых данных var (функции немного странные) использование оператора & приводит к адресу с типом указателя на тип, тип которого является любым типом переменной:

Например, дано

int a;

Выражение &a приводит к адресу, которыйтипа является int *. Аналогично,

struct foo { int x,y,z } s;

Выражение &s приводит к адресу, которыйтипа является struct foo *.

А теперь, пункт вероятной путаницы, учитывая:

int x[5];

Выражение &x приводит к адресу, которыйтипа является int (*)[5], то есть указатель на массив из пяти int. Это заметно отличается от простого x, который, согласно стандарту, вычисляется как адрес, тип которого является указателем на базовый массив тип элемента

Почему разве это имеет значение? Потому что вся арифметика указателей основана на этом фундаментальном типе адреса выражения. Корректировки в нем с использованием типизированной математики указателей зависят от этой фундаментальной концепции.

int x[5];
x + 1

Эффективно делает это:

int x[5];
int *p = x;
p + 1        // results is address of next int

Тогда как:

&x + 1

Эффективно делает это:

int x[5];
int (*p)[5] = &x;
p + 1         // results in address of next int[5] 
              // (which, not coincidentally, there isn't one)

Что касается sizeof() дифференциала, то опять же, эти надоедливые типы возвращаются домой к насесту, и в частности различие, важно отметить что sizeof является оператором времени компиляции, а не времени выполнения:

int x[5]
size_t n = sizeof(x);

В приведенном выше, sizeof(x) приравнивается к sizeof(type-of x). Так как x - это int[5], а int - это, по-видимому, 4 байта в вашей системе, результат 20. Аналогично,

int x[5];
size_t n = sizeof(*x);

Результаты с sizeof(type-of *x) начинают присваиваться n. Поскольку *x относится к типу int, это синонимично sizeof(int). Аспекты времени компиляции, между прочим, делают следующее одинаково действительным, хотя, по общему признанию, это выглядит немного опасным на первый взгляд взгляд:

int *p = NULL;
size_t n = sizeof(*p);

Как и раньше, sizeof(type-of *p) приравнивается к sizeof(int)

Но как насчет:

int x[5];
size_t n = sizeof(&x);

Здесь снова sizeof(&x) приравнивается к sizeof(type-of &x). но мы только что рассмотрели, что такое Тип &x; его int (*)[5]. То есть его тип Datapointer , и поэтому его размер будет равен размеру указателя . На вашей установке, по-видимому, есть 32-битные указатели, так как размер сообщения равен 4.

Пример того, как &x является типом указателя, и это действительно все Данные типы указателей приводят к одинаковому размеру, я закрываю следующим примером:

#include <stdio.h>

int main()
{
    int x[5];
    double y[5];
    struct foo { char data[1024]; } z[5];

    printf("%zu, %zu, %zu\n", sizeof(x[0]), sizeof(x), sizeof(&x));
    printf("%zu, %zu, %zu\n", sizeof(y[0]), sizeof(y), sizeof(&y));
    printf("%zu, %zu, %zu\n", sizeof(z[0]), sizeof(z), sizeof(&z));

    return 0;
}

Выход (Mac OSX 64bit)

4, 20, 8
8, 40, 8
1024, 5120, 8

Обратите внимание, что последние отчеты о размере значенияидентичны .

X имеет тип int[5], поэтому &x-указатель на целочисленный массив из пяти элементов, при добавлении 1 к &x вы увеличиваете до следующего массива из 5 элементов.

Вы сказали: "я получаю большую часть арифметики указателя, пока не увидел следующее:"

int x[5];
sizeof(x)  // equals 20
sizeof(&x) // equals 4 -- sizeof(int))

Исследование первого sizeof...

if ((sizeof(int) == 4) == true) {
   then the size of five tightly packed ints is 5 * 4
   so the result of (sizeof(int[5]) is 20.
}

Однако...

if (size(int)) == 4) is true
   then when the size of the memory holding the value of another memory address is 4,
   ie. when ((sizeof(&(int[5])) == 4) {
      it is a cooincidence that memory addresses conveniently fit 
      into the same amount of memory as an int.
   }
}

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

Чтобы еще больше загнать точку домой

it is true that (sizeof(char[4]) == 4),
but that does not mean that a `char[4]` is a memory address.

Теперь в C оператор смещения для адреса памяти "знают" смещение, основанное на типе указателя, char, int или подразумеваемом размере адреса. Когда вы добавляете указатель, компилятор преобразует это добавление в операцию, которая выглядит примерно так

addressValue += sizeof(addressType)*offsetCount

Где

&x + 1

Становится

x += sizeof(x)*1;

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

int x[5];
int* xPtr = &x;
char* xAsCharPtr = (char*) xPtr;
printf("%d", xAsCharPtr + 2);

Распечатаем число, состоящее примерно из 1/2 битов чисел в x [0] и x[1].

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

Когда x является выражением, оно может быть прочитано как &x[0] из-за неявного преобразования, добавление 1 к этому выражению интуитивно имеет больше смысла, чем мы хотим &x[1]. При выполнении sizeof(x) неявное преобразование не происходит, давая общий размер объекта x. арифметика с &x+1 имеет смысл также при рассмотрении того, что &x является указателем к массиву из 5 элементов.

То, что не становится интуитивным, - это sizeof (&x), можно было бы ожидать, что он также будет иметь размер x, но это размер элемента в массиве, на который указывают, x.