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

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

Сбой следующей программы:

#include <stdio.h>
#include <stddef.h>

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, которое указывает на начальную элемент объекта массива и не является lvalue."

my_test.array имеет тип "array of 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 указывал на первый член массива?

Ответы

Ответ 1

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

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

Ответ 2

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

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;
}

Ответ 3

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

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

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

typedef struct 
{    
    int ptr[];
} type2_t;

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

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

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

Ответ 4

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

Для простоты предположим, что 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).