Этот хак действителен в соответствии со стандартом? - PullRequest
8 голосов
/ 25 июля 2010

Это так же, как структура взломать. Это действительно в соответствии со стандартом 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;

}

Ответы [ 6 ]

6 голосов
/ 25 июля 2010

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

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

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

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

4 голосов
/ 25 июля 2010

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

Интересно, что следующая строка, которую вы не запрашивали, синтаксически действительна, но не дает вам требуемый ответ (за исключением случаягде 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 к пустому указателю и будет обрабатывать его как sizeof(void) == 1, стандарт C не дает вам такого разрешения.

1 голос
/ 25 июля 2010

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

    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 с, и вам (предположительно) нужно указать f->comment на область памяти, которая является num char s после f->data, что было бы больше так:

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

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

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

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 голос
/ 25 июля 2010

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

struct foo {
   size_t size
   char data[1]
}

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

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

0 голосов
/ 27 июля 2010

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

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

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

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);
}

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

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

0 голосов
/ 25 июля 2010

Другая возможная проблема - выравнивание.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...