Как выделить память для массива и структуры в одном вызове malloc, не нарушая строгий псевдоним?

При распределении памяти для массива с переменным размером я часто делаю что-то вроде этого:

struct array {
    long length;
    int *mem;
};

struct array *alloc_array( long length)
{
    struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length);
    arr->length = length;
    arr->mem = (int *)(arr + 1); /* dubious pointer manipulation */
    return arr;
}

Затем я использую атрибут следующим образом:

int main()
{
    struct array *arr = alloc_array( 10);
    for( int i = 0; i < 10; i++)
        arr->mem[i] = i;
    /* do something more meaningful */
    free( arr);
    return 0;
}

Это работает и компилируется без предупреждений. Недавно, однако, я прочитал о строгом псевдониме. Насколько я понимаю, приведенный выше код является законным в отношении строгого сглаживания, поскольку доступ к памяти через int * не является доступом к памяти через struct array *. Действительно ли код нарушает строгие правила псевдонимов? Если да, то как его можно изменить, чтобы не сломать их?

Я знаю, что я мог бы распределять структуру и массив по отдельности, но тогда мне также нужно будет освобождать их отдельно, предположительно, в какой-то функции free_array. Это означало бы, что я должен знать тип памяти, которую освобождаю, когда я ее освобождаю, что усложнит код. Вероятно, это будет медленнее. Это не то, что я ищу.

Ответ 1

Правильный способ объявления гибкого элемента массива в структуре выглядит следующим образом:

struct array {
    long length;
    int mem[];
};

Затем вы можете выделить пространство по-прежнему без необходимости присваивать mem:

struct array *alloc_array( long length)
{
    struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length);
    arr->length = length;
    return arr;
}

Ответ 2

Modern C официально поддерживает гибкие элементы массива. Таким образом, вы можете определить свою структуру следующим образом:

struct array {
    long length;
    int mem[];
};

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

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

Ответ 3

Я считаю, что написанный код нарушает строгие правила псевдонимов, когда стандарт читается в строгом смысле слова.

Вы получаете доступ к объекту типа int через указатель на array несвязанных типов. Я считаю, что простой выход - использовать начальный адрес структуры, а не преобразовать его char * и выполнить на нем арифметику указателя. Пример:

void* alloc = malloc(...);
array = alloc;
int* p_int = (char*)alloc + sizeof(array);