Указатель против массива в C, нетривиальная разность

Я думал, что я действительно это понял, и повторное чтение стандарта (ISO 9899: 1990) просто подтверждает мое явно неправильное понимание, поэтому теперь я спрашиваю здесь.

Следующая программа выйдет из строя:

#include  #include  typedef struct { int array[3]; } type1_t; typedef struct { int *ptr; } type2_t; type1_t my_test = { {1, 2, 3} }; int main(int argc, char *argv[]) { (void)argc; (void)argv; type1_t *type1_p = &my_test; type2_t *type2_p = (type2_t *) &my_test; printf("offsetof(type1_t, array) = %lu\n", offsetof(type1_t, array)); // 0 printf("my_test.array[0] = %d\n", my_test.array[0]); printf("type1_p->array[0] = %d\n", type1_p->array[0]); printf("type2_p->ptr[0] = %d\n", type2_p->ptr[0]); // this line crashes return 0; } 

Сравнивая выражения my_test.array[0] и type2_p->ptr[0] соответствии с моей интерпретацией стандарта:

6.3.2.1 Подпись на основе массива

«Определение индексного оператора [] состоит в том, что E1 [E2] идентичен (* ((E1) + (E2))).

Применяя это:

 my_test.array[0] (*((E1)+(E2))) (*((my_test.array)+(0))) (*(my_test.array+0)) (*(my_test.array)) (*my_test.array) *my_test.array type2_p->ptr[0] *((E1)+(E2))) (*((type2_p->ptr)+(0))) (*(type2_p->ptr+0)) (*(type2_p->ptr)) (*type2_p->ptr) *type2_p->ptr 

type2_p->ptr имеет тип «указатель на int», а значение является начальным адресом my_test . *type2_p->ptr поэтому оценивает целочисленный объект, хранилище которого совпадает с адресом my_test .

В дальнейшем:

6.2.2.1 Lvalues, массивы и обозначения функций

«За исключением случаев, когда это операнд оператора sizeof или оператора унарного & …, значение l, которое имеет тип array of type , преобразуется в выражение с типом pointer to type , указывающее на начальный элемент объекта массива и это не значение ».

my_test.array имеет тип «массив int» и как описано выше, преобразован в «указатель на int» с адресом первого элемента как значения. *my_test.array поэтому оценивает целочисленный объект, чье хранилище находится на том же адресе, что и первый элемент в массиве.

И наконец

6.5.2.1 Спецификации структуры и союза

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

Поскольку первым элементом type1_t является массив, начальный адрес этого и всего объекта type1_t является таким же, как описано выше. Поэтому я понял, что *type2_p->ptr оценивает целое число, чье хранилище находится на том же адресе, что и первый элемент в массиве и, таким образом, идентичен *my_test.array .

Но это не может быть так, потому что программа последовательно падает на Solaris, cygwin и linux с версиями gcc версии 2.95.3, 3.4.4 и 4.3.2, поэтому любая экологическая проблема полностью исключается.

Где мои рассуждения неправильные / чего я не понимаю? Как объявить type2_t, чтобы ptr указывал на первый член массива?

Массив – это своего рода хранилище. Синтаксически он используется как указатель, но физически в этой структуре нет переменной «указатель» – всего три типа. С другой стороны, указатель int является фактическим типом данных, хранящимся в структуре. Поэтому, когда вы выполняете актерский состав, вы, вероятно, * заставляете ptr принимать значение первого элемента в массиве, а именно 1.

* Я не уверен, что это на самом деле определено поведение, но, как минимум, это будет работать на большинстве распространенных систем.

Пожалуйста, простите меня, если я пропущу что-либо в вашем анализе. Но я думаю, что основная ошибка во всем этом заключается в том, что это неправильное предположение

type2_p-> ptr имеет тип «указатель на int», а значение является начальным адресом my_test.

Нет ничего, что заставило бы его иметь такую ​​ценность. Скорее, это очень вероятно, что он указывает где-то

 0x00000001 

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

Кроме того, я очень сомневаюсь, что ваш кастинг для другой структуры действительно действителен (как, гарантированно, работает). Вы можете бросить, а затем прочитать общую начальную последовательность любой структуры, если оба они являются членами объединения. Но они не в вашем примере. Вы также можете наложить указатель на первый элемент. Например:

 typedef struct { int array[3]; } type1_t; type1_t f = { { 1, 2, 3 } }; int main(void) { int (*arrayp)[3] = (int(*)[3])&f; (*arrayp)[0] = 3; assert(f.array[0] == 3); return 0; } 

Где мои рассуждения неправильные / чего я не понимаю?

type_1::array (не строго синтаксис C) не является int * ; это int [3] .

Как объявить type2_t, чтобы ptr указывал на первый член массива?

 typedef struct { int ptr[]; } type2_t; 

Это объявляет гибкий элемент массива. Из стандарта C (пункт 6.7.2.1, пункт 16):

Однако, когда a. (или ->) имеет левый операнд, который является (указателем на) структуру с гибким членом массива, а правый операнд – этим членом, он ведет себя так, как если бы этот элемент был заменен самым длинным массивом (с тем же типом элемента ), который не сделает структуру больше, чем объект, к которому обращаются; смещение массива должно оставаться равным элементу гибкого элемента массива, даже если это будет отличаться от размера массива замены.

type1_t::array есть, он может alias type1_t::array правильно.

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

Для простоты предположим, что my_test находится по адресу 0x80000000.

 type1_p == 0x80000000 &type1_p->my_array[0] == 0x80000000 // my_array[0] == 1 &type1_p->my_array[1] == 0x80000004 // my_array[1] == 2 &type1_p->my_array[2] == 0x80000008 // my_array[2] == 3 

Когда вы набрасываете его на type2_t,

 type2_p == 0x80000000 &type2_p->ptr == 0x8000000 // type2_p->ptr == 1 type2_p->ptr[0] == *(type2_p->ptr) == *1 

Чтобы сделать то, что вы хотите, вам придется либо создать вторичную структуру, либо присвоить адрес массива ptr (например, type2_p-> ptr = type1_p-> my_array) или объявить ptr как массив (или массив переменной длины, например int ptr []).

Кроме того, вы можете получить доступ к элементам уродливым образом: (& type2_p-> ptr) [0] , (& type2_p-> ptr) [1] . Однако будьте осторожны, так как (& type2_p-> ptr) [0] будет фактически int * , а не int . На 64-битных платформах, например, (& type2_p-> ptr) [0] будет фактически 0x100000002 (4294967298).

  • Почему я должен использовать указатель, а не сам объект?
  • Почему «это» - это указатель, а не ссылка?
  • Почему полезен указатель «точка-volatile», например «volatile int * p»?
  • Разница указателей / адресов
  • Почему NULL-указатели различаются по-разному на C и C ++?
  • error: неверная инициализация не-const ссылки типа 'int &' из rvalue типа 'int'
  • Как вернуть std :: string.c_str ()
  • C typedef указателя на структуру
  • Что возвращает sizeof (& array)?
  • C указатель на двумерный массив
  • В чем разница между дальними указателями и ближайшими указателями?
  • Давайте будем гением компьютера.