Это точно так же, как взлом структуры. Действительно ли это допустимо согласно стандарту 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;
}
Да, это полностью верно. И я настоятельно рекомендую сделать это, когда это позволит вам избежать ненужных дополнительных выделений (а также обработки ошибок и фрагментации памяти, которые они влекут за собой). У других могут быть разные мнения.
Между прочим, если ваши данные не void *
, а есть что-то, к чему вы можете получить доступ напрямую, еще проще (и более эффективно, потому что это экономит место и позволяет избежать дополнительных косвенных обращений) объявить вашу структуру как :
struct foo {
size_t num_foo;
type data[];
};
и выделите место для нужного вам объема данных. Синтаксис []
действителен только в C99, поэтому для совместимости с C89 вы должны использовать вместо него [1]
, но это может привести к потере нескольких байтов.
Да, общая идея взлома верна, но, по крайней мере, как я ее читал, вы не совсем правильно ее реализовали. Вот что вы сделали правильно:
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).
Строка, которую вы спрашиваете, верна - как говорили другие.
Интересно, что следующая строка, которую вы не запрашивали, синтаксически верна, но не дает вам желаемого ответа (за исключением случая, когда 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 не дает вам такого разрешения.
Это старая игра, хотя обычная форма похожа на
struct foo {
size_t size
char data[1]
}
, а затем выделяйте пространство сколь угодно большого размера и используйте массив, как если бы он имел желаемый размер.
Это верно, но я бы посоветовал вам найти другой способ, если возможно: есть много шансов облажаться.
Другой возможной проблемой может быть выравнивание.
Если вы просто запрограммируете свои f-> данные
, то можете безопасно, например, преобразовать ваш void *
в double *
и использовать его для чтения / записи double (при условии, что num достаточно велико). Однако в вашем примере вы больше не можете этого делать, поскольку данные f-> могут быть неправильно выровнены. Например, чтобы сохранить двойное значение в f-> data, вам нужно будет использовать что-то вроде memcpy вместо простого преобразования типов.
Я бы предпочел использовать некоторую функцию для динамического распределения данных и правильного их освобождения.
Использование этой уловки избавляет вас только от необходимости инициализировать структуру данных и может привести к очень серьезным проблемам (см. Комментарий Джерри).
Я бы сделал что-то вроде этого:
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 () сохраненным значением длины).
Мне кажется, что такое поведение более безопасно ... возможно, ценой распространения блока данных.