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

Могу ли я "перенастроить" массив, выделив больше пространства для охватывающей структуры?

Честно говоря, такой код действителен или он создает UB?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct __attribute__((__packed__)) weird_struct
{
    int some;
    unsigned char value[1];
};

int main(void)
{
    unsigned char text[] = "Allie has a cat";
    struct weird_struct *ws =
        malloc(sizeof(struct weird_struct) + sizeof(text) - 1);
    ws->some = 5;
    strcpy(ws->value, text);
    printf("some = %d, value = %s\n", ws->some, ws->value);
    free(ws);
    return 0;
}

http://ideone.com/lpByQD

Id никогда не думает, что это действительно так, но кажется, что очереди сообщений SystemV делают именно это: см. справочную страницу.

Итак, если очереди SysV msg могут это сделать, возможно, я тоже смогу это сделать? Я думаю, что Id нахожу это полезным для отправки данных по сети (следовательно, __attribute__((__packed__))).

Или, возможно, это определенная гарантия очередей SysV msg, и я не должен делать что-то подобное в другом месте? Или, возможно, эту технику можно использовать, только я делаю это неправильно? Я понял, что Id лучше спросить.

Этот - 1 в malloc(sizeof(struct weird_struct) + sizeof(text) - 1) заключается в том, что я принимаю во внимание, что один байт распределен так или иначе благодаря unsigned char value[1], поэтому я могу вычесть его из sizeof(text).

4b9b3361

Ответ 1

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

Что-то вроде

struct __attribute__((__packed__)) weird_struct
{
    int some;
    unsigned char value [ ];   //nothing, no 0, no 1, no nothing...
}; 

и позже

struct weird_struct *ws =
    malloc(sizeof(struct weird_struct) + strlen("this to be copied") + 1);

или

struct weird_struct *ws =
    malloc(sizeof(struct weird_struct) + sizeof("this to be copied"));

выполнит эту работу.

Связанный, цитируя стандарт C11, глава §6.7.2.1

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


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

struct line {
  int length;
  char contents[0];
};

struct line *thisline = (struct line *)
  malloc (sizeof (struct line) + this_length);
thisline->length = this_length;

В ISO C90 вам нужно указать contents длину 1, что означает либо ваше свободное пространство, либо усложняет аргумент malloc.

который также отвечает на часть -1 в аргументе malloc(), поскольку sizeof(char) гарантированно будет 1 в C.

Ответ 2

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

struct s1 { char arr[4]; char y; } *p;
int x;
...
p->y = 1;
p->arr[x] = 2;
return p->y;

чтобы рассматривать его как эквивалент:

struct s1 { char arr[4]; char y; } *p;
int x;
...
p->arr[x] = 2;
p->y = 1;
return 1;

избегая дополнительного шага загрузки, без необходимости пессимистически допускать возможность того, что x может равняться 4. Составители качества должны иметь возможность распознавать определенные конструкции, которые включают доступ к массивам за пределами их заявленных границ (например, те, которые содержат указатель на структура с одноэлементным массивом в качестве его последнего элемента) и обрабатывать их разумно, но ничто в Стандарте не потребует, чтобы они это сделали, и некоторые авторы компилятора считают, что разрешение для компиляторов вести себя бессмысленно, следует интерпретировать как приглашение сделать это. Я думаю, что это поведение будет определено даже для случая x==4 (что означает, что компилятор должен был бы разрешить его модификацию y), если запись массива обрабатывалась через что-то вроде: (char*)(struct s1*)(p->arr)[x] = 2;, но Стандарт на самом деле не совсем ясно, нужен ли приведение к struct s1*.