Действительно ли этот взлом допустим согласно стандарту?

Это точно так же, как взлом структуры. Действительно ли это допустимо согласно стандарту C?

 // error check omitted!

    typedef struct foo {
       void *data;
       char *comment;
       size_t num_foo;
    }foo;

    foo *new_Foo(size_t num, blah blah)
    {
        foo *f;
        f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
        f->data = f + 1;            // is this OK?
        f->comment = f + 1 + num;
        f->num_foo = num;
        ...
        return f;

}
8
задан Nyan 25 July 2010 в 15:03
поделиться

6 ответов

Да, это полностью верно. И я настоятельно рекомендую сделать это, когда это позволит вам избежать ненужных дополнительных выделений (а также обработки ошибок и фрагментации памяти, которые они влекут за собой). У других могут быть разные мнения.

Между прочим, если ваши данные не void * , а есть что-то, к чему вы можете получить доступ напрямую, еще проще (и более эффективно, потому что это экономит место и позволяет избежать дополнительных косвенных обращений) объявить вашу структуру как :

struct foo {
    size_t num_foo;
    type data[];
};

и выделите место для нужного вам объема данных. Синтаксис [] действителен только в C99, поэтому для совместимости с C89 вы должны использовать вместо него [1] , но это может привести к потере нескольких байтов.

6
ответ дан 5 December 2019 в 14:00
поделиться

Да, общая идея взлома верна, но, по крайней мере, как я ее читал, вы не совсем правильно ее реализовали. Вот что вы сделали правильно:

    f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
    f->data = f + 1;            // is this OK?

Но это неправильно:

    f->comment = f + 1 + num;

Поскольку f равно foo * , f + 1 + num равно вычисляется в терминах sizeof (foo) - то есть это эквивалентно f [1 + num] - он (пытается) индексировать 1 + num th foo в массиве. Я почти уверен, что это не то, что вам нужно. Когда вы выделяете данные, вы передаете sizeof (foo) + num + MAX_COMMENT_SIZE , поэтому вы выделяете место для num char s, и то, что вы (предположительно) хочу указать f-> comment на место в памяти, которое num char s после f-> data , что будет примерно так:

f->comment = (char *)f + sizeof(foo) + num;

Приведение f к char * заставляет математику выполняться в терминах char s вместо foo s.

OTOH, поскольку вы всегда выделяете MAX_COMMENT_SIZE для комментария , я бы, вероятно, немного упростил (совсем) и использовал что-то вроде этого:

typedef struct foo {
   char comment[MAX_COMMENT_SIZE];
   size_t num_foo;
   char data[1];
}foo;

А затем разместите его так:

foo *f = malloc(sizeof(foo) + num-1);
f->num_foo = num;

, и он будет работать вообще без каких-либо манипуляций с указателем. Если у вас есть компилятор C99, вы можете немного изменить его:

typedef struct foo {
   char comment[MAX_COMMENT_SIZE];
   size_t num_foo;
   char data[];
}foo;

и выделить:

foo *f = malloc(sizeof(foo) + num);
f->num_foo = num;

Это имеет дополнительное преимущество, которое фактически благословляет стандарт, хотя в этом случае преимущество довольно незначительное (я считаю, что версия с data [1] будет работать со всеми существующими компиляторами C89 / 90).

1
ответ дан 5 December 2019 в 14:00
поделиться

Строка, которую вы спрашиваете, верна - как говорили другие.

Интересно, что следующая строка, которую вы не запрашивали, синтаксически верна, но не дает вам желаемого ответа (за исключением случая, когда num == 0 ).

typedef struct foo
{
   void *data;
   char *comment;
   size_t num_foo;
} foo;

foo *new_Foo(size_t num, blah blah)
{
    foo *f;
    f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
    f->data = f + 1;            // This is OK
    f->comment = f + 1 + num;   // This is !!BAD!!
    f->num_foo = num;
    ...
    return f;
}

Значение f + 1 - это foo * (неявно приведенное к void * присваиванием).

Значение f + 1 + num также является foo * ; он указывает на num + 1 th foo .

Вы, вероятно, имели в виду:

foo->comment = (char *)f->data + num;

Или:

foo->comment = (char *)(f + 1) + num;

Обратите внимание, что GCC позволит вам добавить num в указатель void, и он будет обрабатывать его, как если бы sizeof (void) == 1 , Стандарт C не дает вам такого разрешения.

4
ответ дан 5 December 2019 в 14:00
поделиться

Это старая игра, хотя обычная форма похожа на

struct foo {
   size_t size
   char data[1]
}

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

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

1
ответ дан 5 December 2019 в 14:00
поделиться

Другой возможной проблемой может быть выравнивание.

Если вы просто запрограммируете свои f-> данные , то можете безопасно, например, преобразовать ваш void * в double * и использовать его для чтения / записи double (при условии, что num достаточно велико). Однако в вашем примере вы больше не можете этого делать, поскольку данные f-> могут быть неправильно выровнены. Например, чтобы сохранить двойное значение в f-> data, вам нужно будет использовать что-то вроде memcpy вместо простого преобразования типов.

0
ответ дан 5 December 2019 в 14:00
поделиться

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

Использование этой уловки избавляет вас только от необходимости инициализировать структуру данных и может привести к очень серьезным проблемам (см. Комментарий Джерри).

Я бы сделал что-то вроде этого:

typedef struct foo {
       void *data;
       char *comment;
       size_t num_foo;
    }foo;
foo *alloc_foo( void * data, size_t data_size, const char *comment)
{
   foo *elem = calloc(1,sizeof(foo));
   void *elem_data = calloc(data_size, sizeof(char));
   char *elem_comment = calloc(strlen(comment)+1, sizeof(char));
   elem->data = elem_data;
   elem->comment = elem_comment;

   memcpy(elem_data, data, data_size);
   memcpy(elem_comment, comment, strlen(comment)+1);
   elem->num_foo = data_size + strlen(comment) + 1;
}

void free_foo(foo *f)
{
   if(f->data)
      free(f->data);
   if(f->comment)
      free(f->comment);
   free(f);
}

Обратите внимание, что я не проверял достоверность данных, и мое распределение можно оптимизировать (заменив вызовы strlen () сохраненным значением длины).

Мне кажется, что такое поведение более безопасно ... возможно, ценой распространения блока данных.

0
ответ дан 5 December 2019 в 14:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: