Оперирует ли порядок членов в структуре?

Я нашел своеобразное поведение в C. Рассмотрим приведенный ниже код:

 struct s {
     int a;
 };      

 struct z {
     int a;
     struct s b[];
 };  

 int main(void) {
     return 0;
 }   

Он компилируется просто отлично. Затем измените порядок элементов struct z так:

struct z {
    struct s b[];
    int a; 
};  

И вдруг мы получаем ошибку компиляции field has incomplete type 'struct s []'.

Почему это?

Ответ 1

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

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

  • Никогда не может быть более одного такого члена,
  • Если присутствует, гибкий элемент должен быть последним в struct и
  • struct должен иметь хотя бы один элемент в дополнение к гибкому.

Взгляните на этот Q & A для небольшой иллюстрации об использовании гибких элементов структуры.

Ответ 2

Компилятор не может вычислить, сколько памяти struct s b[]; будет потребляться. Это означает, что если структура имеет какие-либо поля после нее, компилятор не может определить, где находятся эти поля.

Раньше он был (в старых версиях C), который (например,) struct s b[]; не был разрешен как член структуры. Это привело к раздражающему управлению памятью. Для простого примера предположим, что у вас есть структура, содержащая строку "name" (это может быть всего несколько символов или их много). Вы можете использовать массив фиксированного размера, который достаточно велик для самого большого имени (который тратит пространство) или использует указатель и выделяет 2 части памяти (один для структуры и один для строки с именем переменной длины). Кроме того, вы можете использовать указатель и указывать на дополнительное пространство за концом структуры, что заканчивается примерно так:

    length = strlen(my_string);
    foo = malloc(sizeof(MYSTRUCTURE) + length + 1);
    foo->name = (void *)foo + sizeof(MYSTRUCTURE);   // Set pointer to extra bytes past end of structure
    memcpy(foo->name, my_string, length + 1);

Это был самый эффективный вариант; но он также уродлив и подвержен ошибкам.

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

Ответ 3

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

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

Ваш член struct s b[]; предназначен для доступа к динамическим распределениям кучи для нескольких элементов struct s.

Ответ 4

Название вашего вопроса: "Имеет ли порядок членов в struct вопрос?".

Очевидная проблема в вашем коде связана с тем, что ваш struct содержит гибкий элемент.

Итак, вот дополнительная проблема, связанная с общим вопросом о порядке членов в struct:


Возьмем следующие две структуры, например:

struct s1
{
    int a;
    short b;
    char c;
};

struct s2
{
    char c;
    short b;
    int a;
};

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

Итак, struct s2 может в конечном итоге скомпилироваться в:

struct s2
{
    char c;
    char pad1;
    short b;
    short pad2;
    short pad3;
    int a;
};

В конечном итоге это приведет к разным размерам для экземпляров типов struct s1 и struct s2.

Ответ 5

В этом случае порядок имеет значение. Ваш struct z содержит массив, состоящий из structs s. Однако этот массив не имеет связанного с ним размера, поэтому компилятор не знает, как распределить соответствующее пространство стека, так как после этого есть другое поле структуры (int a). Пример того, как это работает:

struct s {
  int a;
}

struct z {
  struct s b[10];
  int a;
}

int main(void) {
  return 0;
}

Если вам действительно нужен массив для изменения размера, лучше всего выделить всю структуру в куче с массивом как указатель на struct s, а затем динамически перераспределить его для изменения размера массива. Посмотрите malloc (3), realloc 3), calloc (3) и free (3).