Как включить динамический массив ВНУТРИ структуры в C? - PullRequest
42 голосов
/ 14 января 2010

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

 #include <stdlib.h>

struct my_struct {
    int n;
    char s[]
};

int main()
{
    struct my_struct ms;
    ms.s = malloc(sizeof(char*)*50);
}

и вот ошибка, которую дает мне gcc: ошибка: неверное использование элемента гибкого массива

Я могу заставить его скомпилироваться, если я объявлю объявление s внутри структуры равным

char* s

и это, вероятно, лучшая реализация (арифметика с указателями быстрее, чем у массивов, да?) но я подумал в декларации

char s[]

совпадает с

char* s

Ответы [ 7 ]

67 голосов
/ 14 января 2010

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

#include <stdlib.h>

struct my_struct {
    int n;
    char s[];
};

Когда вы выделяете для этого место, вы хотите выделить размер структуры плюс количество пространства, которое вы хотите для массива:

struct my_struct *s = malloc(sizeof(struct my_struct) + 50);

В этом случае член гибкого массива представляет собой массив char, а sizeof (char) == 1, поэтому вам не нужно умножать его на размер, а так же, как и любому другому malloc, если это был массив другого типа:

struct dyn_array { 
    int size;
    int data[];
};

struct dyn_array* my_array = malloc(sizeof(struct dyn_array) + 100 * sizeof(int));

Редактировать: Это дает другой результат, чем изменение члена на указатель. В этом случае вам (обычно) нужны два отдельных выделения, одно для самой структуры, а другое для «дополнительных» данных, на которые указывает указатель. Используя член гибкого массива, вы можете разместить все данные в одном блоке.

19 голосов
/ 14 января 2010

Сначала вам нужно решить, что именно вы пытаетесь сделать.


Если вы хотите иметь структуру с указателем на [независимый] массив внутри, вы должны объявить ее как

struct my_struct { 
  int n; 
  char *s;
}; 

В этом случае вы можете создать реальный объект структуры любым удобным вам способом (например, автоматической переменной)

struct my_struct ms;

и затем выделять память для массива независимо

ms.s = malloc(50 * sizeof *ms.s);  

На самом деле, нет общей необходимости динамически выделять память массива

struct my_struct ms;
char s[50];

ms.s = s;

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

Обратите внимание, что в этом случае у вас есть два независимых блока памяти: структура и массив.


Совершенно другим подходом было бы использование идиомы "struct hack". В этом случае массив становится неотъемлемой частью структуры. Оба находятся в одном блоке памяти. В C99 структура будет объявлена ​​как

struct my_struct { 
  int n; 
  char s[];
}; 

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

struct my_struct *ms = malloc(sizeof *ms + 50 * sizeof *ms->s);

Размер блока памяти в этом случае рассчитывается для размещения членов структуры и конечного массива во время выполнения.

Обратите внимание, что в этом случае у вас нет возможности создавать такие объекты структуры, как статические или автоматические объекты. Структуры с гибкими элементами массива в конце могут быть динамически размещены только в C.


Ваше предположение о том, что арифметика указателей быстрее, чем массивы, абсолютно неверно. Массивы работают по арифметике указателей по определению, поэтому они в основном одинаковы. Более того, подлинный массив (не распавшийся на указатель) обычно немного быстрее объекта указателя. Значение указателя должно быть прочитано из памяти, а расположение массива в памяти «известно» (или «вычислено») из самого объекта массива.

1 голос
/ 14 января 2010

Использование массива неопределенного размера допускается только в конце структуры и работает только в некоторых компиляторах. Это нестандартное расширение компилятора. (Хотя я думаю, что C ++ 0x будет позволять это.)

Массив не будет выделяться отдельно от структуры. Так что вам нужно выделить все my_struct, а не только часть массива.

Что я делаю, так это просто присваиваю массиву небольшой, но ненулевой размер. Обычно 4 для символьных массивов и 2 для wchar_t массивов, чтобы сохранить 32-битное выравнивание.

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

Кроме того, я думаю, что вы не должны использовать sizeof (char *) в своем распределении.

Это то, что я бы сделал.

struct my_struct {
    int nAllocated;
    char s[4]; // waste 32 bits to guarantee alignment and room for a null-terminator
};

int main()
{
    struct my_struct * pms;
    int cb = sizeof(*pms) + sizeof(pms->s[0])*50;
    pms = (struct my_struct*) malloc(cb);
    pms->nAllocated = (cb - sizoef(*pms) + sizeof(pms->s)) / sizeof(pms->s[0]);
}
0 голосов
/ 14 января 2010

Я подозреваю, что компилятор не знает, сколько места ему нужно выделить для s [], если вы решите объявить с ним автоматическую переменную.

Я согласен с тем, что сказал Бен, объявляю вашу структуру

struct my_struct {
    int n;
    char s[1];
};

Кроме того, чтобы уточнить свой комментарий о хранилище, объявление char *s не поместит структуру в стек (так как она динамически распределяется) и не выделит s в куче, что он будет интерпретировать как первый sizeof(char *) байтов вашего массива в качестве указателя, поэтому вы не будете работать с данными, которые, как вы думаете, вам нужны, и, вероятно, будут фатальными.

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

0 голосов
/ 14 января 2010

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

и BTW - сделай это с ++ и используй вектор

0 голосов
/ 14 января 2010

арифметика указателей быстрее, чем у массивов, да?

Совсем нет - на самом деле они одинаковы. массивы переводятся в арифметику указателей во время компиляции.

char test[100];
test[40] = 12;

// translates to: (test now indicates the starting address of the array)
*(test+40) = 12;
0 голосов
/ 14 января 2010

Массивы преобразуются в указатели, и здесь вы должны определить s как char *s. Структура в основном является контейнером, и (IIRC) должна быть фиксированного размера, поэтому наличие динамически изменяемого массива внутри нее просто невозможно. Так как вы все равно malloc запоминаете память, это не должно иметь никакого значения в том, что вы ищете.

По сути, вы говорите, s будет указывать место в памяти. Обратите внимание, что вы все еще можете получить доступ к этому позже, используя обозначения, такие как s[0].

...