#c #c #language-lawyer #padding
#c #c #язык-юрист #набивка
Вопрос:
В C я хочу разместить char
идентификатор в самом конце структуры, чтобы я мог различать тип структуры от указателя до конца структуры (выделенный динамически). Очевидно, что возможность заполнения в конце затрудняет это. Я подумал о двух подходах.
Первый подход заключается в размещении массива символов, который простирается до конца структуры, чтобы (char*)ptr_to_end - 1
всегда указывать на допустимый символ. Я думаю, что это должно сработать, если компилятор не делает никаких забавных дел. В противном случае он не сможет скомпилироваться:
typedef struct { int foo; int bar; char type; } MyStructDummy; typedef struct { int foo; int bar; char type[ sizeof( MyStructDummy ) - offsetof( MyStructDummy, type ) ]; } MyStruct; _Static_assert( sizeof( MyStruct ) == sizeof( MyStructDummy ), "Could not ensure char at end of MyStruct" );
Второй подход заключается offsetof
в том, чтобы всегда обращаться к блоку malloc-ed как к отдельным переменным (членам), а не как к полной структуре. Таким образом, мы избегаем когда-либо передавать тип структуры как эффективный тип по всему блоку или случайно изменять значения заполнения:
typedef struct { int foo; int bar; char type; } MyStruct; int *MyStruct_foo( void *end_ptr ) { return (int*)( (char*)end_ptr - sizeof( MyStruct ) offsetof( MyStruct, foo ) ); } int *MyStruct_bar( void *end_ptr ) { return (int*)( (char*)end_ptr - sizeof( MyStruct ) offsetof( MyStruct, bar ) ); } char *MyStruct_type( void *end_ptr ) { return (char*)end_ptr - 1; }
Является ли один из этих подходов предпочтительнее другого? Существует ли существующая идиома C, которая позволяет достичь того, чего я хочу (я не могу использовать гибкий элемент массива, потому что хочу поддерживать совместимость с C )?
Спасибо!
Редактировать:
Карл спросил, как может быть полезно разместить идентификатор в конце структуры. Рассмотрим эту экономящую память реализацию динамического массива/вектора:
//VecHdr is for vectors without an automatic element destructor function //and whose capacity is lt; UINT_MAX typedef struct { alignas( max_align_t ) unsigned int size; unsigned int cap; char type_and_flags; //At very end } VecHdr; //Probable size: 16 bytes //VecHdr is for vectors with an element destructor or whose capacity is gt;= UINT_MAX typedef struct { alignas( max_align_t ) size_t size; size_t cap; void (*element_destructor_func)( void* ); char type_and_flags; //At very end } VecHdrEx; //Probable size: 32 bytes //... int *foo = vec_create( int ); //This macro returns a pointer to a malloced block of ints, preceded by a VecHdr int *bar = vec_create_ex( int, my_element_destructor ); //This macro returns a pointer to malloced block of ints, preceded by a VecHdrEx vec_push( foo, 12345 ); //vec_push knows that foo is preceded by a VecHdr by checking (char*)foo - 1 //foo's VecHdr may eventually be replaced with a VecHdrEx if we add enough elements vec_push( bar, 12345 ); //vec_push knows that bar is preceded by a VecHdrEx by checking (char*)foo - 1
Комментарии:
1. » Я хочу разместить
char
идентификатор в самом конце структуры » — почему?», чтобы я мог определить тип структуры по указателю на конец структуры » — почему вы обращаетесь к структуре с конца, а не спереди?2. @JDormer Я думаю, что использование памяти должно быть одинаковым в обоих подходах. Либо эта память попадает в
char
массив, либо используется для заполнения.3. Я до сих пор не могу понять, какую проблему вы пытаетесь решить с помощью такого подхода. Пожалуйста, покажите пример кода, в котором вы в противном случае столкнулись с проблемой из-за отсутствия необходимой информации об указателе, и объясните, почему вы могли оказаться в такой ситуации.
4. Я бы поместил основной блок заголовка постоянного размера перед массивом, видимым пользователем, и еще один дополнительный блок заголовка с дополнительными данными перед основным блоком заголовка. Его наличие и размер определяются полем тип в основном блоке.
5. смещение кажется достаточным. В этот момент не должно иметь значения, куда вы поместили символ. Вы также можете использовать int (или два), чтобы практически гарантировать выравнивание, хотя это не гарантировано
Ответ №1:
Заполнение будет только в конце, если последний элемент является чем-то неровным, например, малым целочисленным типом.
Однако если вы сделаете последний элемент гибким элементом массива символьного типа, он всегда будет помещен поверх таких байтов заполнения, потому что структура не учитывает элемент гибкого массива при определении размера и заполнения.
Пример:
typedef struct { int foo; int bar; char type[]; } MyStructDummy; MyStructDummy* dummy = malloc (sizeof *dummy 1); printf("Size: %zun", sizeof(MyStructDummy)); printf("Address of struct:%pn", dummy); printf("Address of type:%pn", dummy-gt;type);
Это дает что-то вроде:
Size: 8 Address of struct:0x4072a0 Address of type:0x4072a8
Если мы добавим дополнительный элемент, чтобы убедиться, что в конце есть заполнение:
typedef struct { int foo; int bar; char causing_padding; char type[]; } MyStructDummy;
Затем печатается тот же код, что и выше:
Size: 12 Address of struct:0x16f22a0 Address of type:0x16f22a9
Итак, здесь компилятор добавил дополнение, но это позволяет нам использовать байт 9 для данных. В конечном итоге мы выделяем память за пределами гибкого элемента массива. Теперь мы могли бы вместо этого выделить гибкий элемент массива, чтобы покрыть все отступы:
size_t trailing_padding = sizeof(MyStructDummy) - offsetof(MyStructDummy, type); MyStructDummy* dummy = malloc (sizeof *dummy trailing_padding);
Это все еще остается type
по адресу 9, но теперь оно занимает 3 байта. Мы могли бы запомнить их все с помощью любого кода, который вы хотите туда поместить. Это четко определено и переносимо. Полный пример:
#include lt;stdio.hgt; #include lt;stdlib.hgt; #include lt;stddef.hgt; #include lt;string.hgt; typedef struct { int foo; int bar; char causing_padding; char type[]; } MyStructDummy; int main (void) { size_t trailing_padding = sizeof(MyStructDummy) - offsetof(MyStructDummy, type); MyStructDummy* dummy = malloc (sizeof *dummy trailing_padding); memset(dummy-gt;type, 42, trailing_padding); // write code 42 to all bytes printf("Size: %zun", sizeof(MyStructDummy)); printf("Address of struct:%pn", dummy); printf("Address of type:%pn", dummy-gt;type); unsigned char* endptr = (unsigned char*)dummy sizeof(*dummy) - 1; printf("Value of last byte: %d", *endptr); }
Выход:
Size: 12 Address of struct:0xa842a0 Address of type:0xa842a9 Value of last byte: 42