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

Массив размера 0 в конце структуры

Мой преподаватель курса системного программирования, который я беру, сказал нам сегодня, чтобы определить структуру с массивом нулевой длины в конце:

struct array{
    size_t size;
    int data[0];
};

typedef struct array array;

Это полезная структура для определения или инициализации массива с переменной, то есть следующим образом:

array *array_new(size_t size){
    array* a = malloc(sizeof(array) + size * sizeof(int));

    if(a){
        a->size = size;
    }

    return a;
}

То есть, используя malloc(), мы также выделяем память для массива нулевого размера. Это совершенно новое для меня, и это кажется странным, потому что, по моему мнению, структуры не имеют своих элементов в непрерывных местах.

Почему код в array_new выделяет память на data[0]? Почему это было бы законным для доступа, скажем

array * a = array_new(3);
a->data[1] = 12;

?

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

Я также видел, что это всего лишь функция gcc и не определен никаким стандартом. Это правда?

4b9b3361

Ответ 1

В настоящее время существует стандартная функция, как указано в C11, глава §6.7.2.1, называемая гибким элементом массива.

Цитируя стандарт,

В качестве особого случая последний элемент структуры с более чем одним именованным элементом может имеют неполный тип массива; это называется гибким элементом массива. В большинстве ситуаций, гибкий элемент массива игнорируется. В частности, размер структуры выглядит так, как если бы гибкий элемент массива был исключен, за исключением того, что он может иметь более длинное дополнение, чем упущение будет означать. [...]

Синтаксис должен быть

struct s { int n; double d[]; };

где последний элемент является неполным, (без параметров массива, даже не 0).

Итак, ваш код должен выглядеть лучше

struct array{
    size_t size;
    int data[ ];
};

для стандартного соответствия.

Теперь, приблизившись к вашему примеру из массива 0-го размера, это было унаследованным способом ( "хак-структура" ) для достижения того же. До C99, GCC поддерживал это как расширение для эмуляции гибких функций члена массива.

Ответ 2

Ваш профессор смущен. Они должны прочитать что произойдет, если я определяю массив нулевого размера. Это нестандартное расширение GCC; это недействительно C, а не то, чему они должны научить студентов использовать (*).

Вместо этого используйте стандартный элемент гибкого массива C. В отличие от вашего массива нулевого размера, он действительно будет работать, переносимо:

struct array{
    size_t size;
    int data[];
};

Элементы гибкого массива гарантированно считаются нулевыми, когда вы используете sizeof в структуре, что позволяет делать такие вещи, как:

malloc(sizeof(array) + sizeof(int[size]));

(*) В 90-е годы люди использовали небезопасный эксплойт для добавления данных после структур, известных как "халтур структуры". Чтобы обеспечить безопасный способ расширения структуры, GCC реализовал функцию массива нулевого размера как нестандартное расширение. Он стал устаревшим в 1999 году, когда, наконец, C-стандарт обеспечил лучший способ сделать это.

Ответ 3

В других ответах объясняется, что массивы с нулевой длиной - это расширение GCC, а C - массив переменной длины, но никто не обратился к вашим другим вопросам.

из моего понимания, структуры не имеют своих элементов обязательно в непрерывных местах.

Да. struct тип данных не имеет своих элементов в непрерывных местах.

Почему код в array_new выделяет память на data[0]? Почему это было бы законным для доступа, скажем

array * a = array_new(3);
a->data[1] = 12;

?

Следует отметить, что одно из ограничений на массив нулевой длины состоит в том, что он должен быть последним членом структуры. Таким образом, компилятор знает, что структура может иметь объект переменной длины, а во время выполнения потребуется еще немного памяти.
Но вас не следует путать; "поскольку массив нулевой длины является последним членом структуры, тогда память, выделенная для массива нулевой длины, должна быть добавлена ​​в конец структуры, и поскольку структуры не имеют своих элементов, обязательно в непрерывных местоположениях, то как это распределенная память может быть доступ?"

Нет. Это не так. Распределение памяти для элементов структуры необязательно должно быть смежным, между ними может быть отступы, но к этой выделенной памяти следует обращаться с переменной data. И да, прокладка не будет иметь никакого эффекта здесь. Это правило: §6.7.2.1/15

Внутри объекта структуры небитовые поля и единицы, в которых бит-поля имеют адреса, которые увеличиваются в том порядке, в котором они объявлены.


Я также видел, что это всего лишь функция gcc и не определен никаким стандартом. Это правда?

Да. В качестве других ответов уже упоминалось, что массивы нулевой длины не поддерживаются стандартом C, а являются расширением компиляторов GCC. В C99 введен гибкий элемент массива. Пример из стандарта C (6.7.2.1):

После объявления:

struct s { int n; double d[]; };

структура struct s имеет гибкий элемент массива d. Типичный способ использования:

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

и при условии, что вызов malloc завершается успешно, объект, на который указывает p, ведет себя для большинства целей, как если бы p был объявлен как:

struct { int n; double d[m]; } *p;

(существуют обстоятельства, при которых эта эквивалентность нарушена, в частности, смещения члена d могут быть не одинаковыми).

Ответ 4

Более стандартным способом было бы определить ваш массив с размером данных 1, например:

struct array{
    size_t size;
    int data[1]; // <--- will work across compilers
};

Затем в вычислении используйте смещение элемента данных (а не размера массива):

array *array_new(size_t size){
    array* a = malloc(offsetof(array, data) + size * sizeof(int));

    if(a){
        a->size = size;
    }

    return a;
}

Это эффективно использует array.data в качестве маркера для того, куда могут идти дополнительные данные (в зависимости от размера).

Ответ 5

То, как я это делал, не имеет фиктивного члена в конце структуры: размер самой структуры сообщает вам адрес, который только что прошел. Добавляет 1 к введенному указателю:

header * p = malloc (sizeof (header) + buffersize);
char * buffer = (char*)(p+1);

Что касается структур вообще, вы можете знать, что поля выкладываются по порядку. Возможность сопоставить некоторую наложенную структуру, необходимую бинарному изображению формата файла, вызову операционной системы или оборудованию, является одним из преимуществ использования C. Вы должны знать, как работает отступы для выравнивания, но они находятся в порядке и в одном непрерывном блоке.