2010-07-25 4 views
8

Это точно так же, как структурный хак. Действительно ли он соответствует стандарту 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

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

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

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

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

+0

Как вы знаете, но, вероятно, стоит отметить, гибкий член массива в конце структуры работает только тогда, когда у вас есть один элемент переменной длины. Вопрос имеет два элемента переменной длины ('data' и' comment') и не может легко использовать эту технику. –

+0

В этом случае сохраните 'комментарий' как обычную строку' malloc() '-allocated. –

+0

FWIW, я дал этот ответ перед добавлением поля комментария. При добавлении комментария следуйте совету Донала. –

1

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

struct foo { 
    size_t size 
    char data[1] 
} 

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

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

+0

Этот старый хак не является * действительным * (строго) в соответствии со стандартом. – Nyan

+0

Нет, это не так, но он * будет * работать с любой соответствующей реализацией 'malloc'. :: немного размышляет о патологически сегментированных архитектурах памяти :: * Почти * любой соответствующий 'malloc'. – dmckee

+1

Это справедливо как следствие других требований стандарта, например требование о том, что если одна структура соответствует начальной части другой, то доступ к этим элементам через 2 совместим. C99 явно поддерживает гибкую нотацию массива '[]', но старый '[1]' метод ** обязательно будет работать тоже! –

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+numthfoo в массиве. Я почти уверен, что это не что вы хотите. Когда вы выделяете данные, вы передаете sizeof(foo)+num+MAX_COMMENT_SIZE, поэтому вы выделяете пространство для num char s, и то, что вы (предположительно) хотите, - это указать f->comment на то место в памяти, которое находится num char с после f->data, что было бы более как это:

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

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

Ото, так как вы всегда выделяя 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; 

Это имеет дополнительное преимущество в том, что стандарт на самом деле благословляет его, хотя и в этом случае преимущество довольно minor (я считаю, что версия с будет работать с каждым компилятором C89/90).

+0

Вы заметили это примерно в то же время, что и я, - мы составляли наши ответы параллельно. –

+0

@Jonathan Leffler: Yup - особенно с таким подробным ответом, как у меня, это почти неизбежно ... –

4

Линия, которую вы задаете, действительна - как говорили другие.

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

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

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

Или:

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

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

0

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

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

+0

Использование моего подхода (массив в конце) автоматически решает проблему выравнивания. Это также можно решить, выделив достаточно дополнительного пространства, чтобы вы могли округлить 'f-> data' до следующего кратного' sizeof (double) 'или любого другого типа, который вам нужен. –

0

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

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

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

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() вызывает по сохраненному значению длины).

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

Смежные вопросы