Указатель против массива в C, нетривиальная разница - PullRequest
11 голосов
/ 19 марта 2009

Мне показалось, что я действительно понял это, и перечитывание стандарта (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 имеет тип "pointer to int", а значением является начальный адрес my_test. *type2_p->ptr поэтому оценивается как целочисленный объект, чье хранилище находится по тому же адресу, что и у my_test.

Далее:

6.2.2.1 L-значения, массивы и функциональные обозначения

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

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

И наконец

6.5.2.1 Структура и спецификаторы объединения

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

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

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

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

Ответы [ 4 ]

11 голосов
/ 19 марта 2009

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

type2_p-> ptr имеет тип "pointer to 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;
}
10 голосов
/ 19 марта 2009

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

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

3 голосов
/ 19 марта 2009

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

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

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

typedef struct 
{    
    int ptr[];
} type2_t;

Это объявляет член гибкого массива. Из стандарта С (пункт 6.7.2.1):

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

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

0 голосов
/ 18 октября 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...