Po co tablica z zerowymi elementami?

W kodzie jądra Linuksa znalazłem następującą rzecz, której nie mogę zrozumieć.

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));

Kod jest tutaj: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

Jaka jest potrzeba i cel tablicy danych z zerowymi elementami?

Author: S.S. Anne, 2013-02-01

5 answers

Jest to sposób na zmienne rozmiary danych, bez konieczności wywoływania malloc (kmalloc w tym przypadku) dwa razy. Użyłbyś go tak:

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);

To nie było standardowe i było uważane za hack (jak powiedział Aniket), ale było standaryzowane w C99 . Obecnie standardowym formatem jest:

struct bts_action {
     u16 type;
     u16 size;
     u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */

Zauważ, że nie podajesz żadnego rozmiaru dla pola data. Zauważ również, że ta specjalna zmienna może pojawić się tylko na końcu struktury.


W C99, to Materia jest wyjaśniona w 6.7.2.1.16 (akcent mine):

Jako szczególny przypadek, ostatni element struktury z więcej niż jednym członem może mają niekompletny typ tablicy; nazywa się to elastycznym elementem tablicy . W większości sytuacji, elastyczny element tablicy jest ignorowany. W szczególności wielkość konstrukcji jest tak, jakby elastyczny element tablicy został pominięty, z wyjątkiem tego, że może mieć więcej padding niż to przeoczenie sugerowałoby. Jednak, gdy a . (lub ->) operator ma lewy operand, który jest (wskaźnik do) struktury z elastycznym elementem tablicy i odpowiednimi nazwami operandów, które member, zachowuje się tak, jakby ten member został zastąpiony najdłuższą tablicą (z tą samą typu elementu), które nie uczyniłyby struktury większej niż obiekt, do którego dostęp przesunięcie tablicy pozostaje przesunięciem elementu tablicy elastycznej, nawet jeśli różniłoby się to z tablicy zastępczej. Gdyby tablica ta nie miała elementów, zachowuje się tak, jakby miał JEDEN element, ale zachowanie jest nieokreślone, jeśli zostanie podjęta jakakolwiek próba uzyskania dostępu do tego element lub wygenerować wskaźnik obok niego.

Lub innymi słowy, jeśli masz:

struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);

Możesz uzyskać dostęp do var->data z indeksami w [0, extra). Zauważ, że sizeof(struct something) Da tylko rozmiar uwzględniający inne zmienne, tzn. daje data Rozmiar 0.


Interesujące może być również zwrócenie uwagi na to, jak norma faktycznie podaje przykłady mallocing taka konstrukcja (6.7.2.1.17):

struct s { int n; double d[]; };

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

Kolejną ciekawą notką standardu w tym samym miejscu jest (podkreślenie moje):

Zakładając, że wywołanie malloc powiedzie się, obiekt wskazywany przez p zachowuje się, dla większości celów, tak jakby P został zadeklarowany jako:

struct { int n; double d[m]; } *p;

(istnieją okoliczności, w których równoważność ta jest łamana; w szczególności kompensaty członka d mogą nie być takie same ).

 142
Author: Shahbaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 17:48:00

To jest właśnie hack, dla GCC (C90 ) w rzeczywistości.

Nazywa się również strukturą hack .

Więc następnym razem powiem:

struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);

Będzie równoznaczne z powiedzeniem:

struct bts_action{
    u16 type;
    u16 size;
    u8 data[100];
};

I mogę utworzyć dowolną ilość takich obiektów struct.

 37
Author: Aniket Inge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-05 23:50:09

Chodzi o to, aby pozwolić na tablicę o zmiennej wielkości na końcu struktury. Prawdopodobnie bts_action jest pakietem danych z nagłówkiem o stałej wielkości (pola type i size) i członkiem o zmiennej wielkości data. Deklarując ją jako tablicę o długości 0, może być indeksowana tak jak każda inna tablica. Następnie przydzieliłbyś bts_action strukturę, powiedzmy 1024-bajtowądata O rozmiarze, tak:

size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);

Zobacz też: http://c2.com/cgi/wiki?StructHack

 7
Author: sheu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-01 09:48:52

Kod nie jest prawidłowy C (Zobacz to ). Jądro Linuksa, z oczywistych powodów, nie jest w najmniejszym stopniu związane z przenośnością, więc używa dużo niestandardowego kodu.

To, co robią, to niestandardowe rozszerzenie GCC o rozmiarze tablicy 0. Program zgodny ze standardem napisałby u8 data[]; i oznaczałby to samo. Autorzy jądra Linuksa najwyraźniej uwielbiają robić rzeczy niepotrzebnie skomplikowane i niestandardowe, jeśli taka opcja ujawni siebie.

W starszych standardach C zakończenie struktury pustą tablicą było znane jako "Hack struktury". Inni już wyjaśnili jej cel w innych odpowiedziach. Hack struktury, w standardzie C90, był niezdefiniowanym zachowaniem i mógł powodować awarie, głównie dlatego, że kompilator C może swobodnie dodawać dowolną liczbę bajtów wypełnienia na końcu struktury. Takie bajty wypełnienia mogą kolidować z danymi, które próbowałeś "włamać"na końcu struktury.

GCC na początku wykonało niestandardowe rozszerzenie aby zmienić to z niezdefiniowanego na dobrze zdefiniowane zachowanie. Standard C99 zaadaptował tę koncepcję i każdy nowoczesny program C może korzystać z tej funkcji bez ryzyka. Jest znany jako flexible array member w C99/C11.

 6
Author: Lundin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:20

Innym zastosowaniem tablicy o zerowej długości jest nazwana Etykieta wewnątrz struktury, która pomaga w sprawdzaniu przesunięcia struktury w czasie kompilacji.

Załóżmy, że masz jakieś duże definicje struktur (obejmujące wiele linii pamięci podręcznej), które chcesz upewnić się, że są wyrównane do granicy linii pamięci podręcznej zarówno na początku, jak i w środku, gdzie przekracza granicę.

struct example_large_s
{
    u32 first; // align to CL
    u32 data;
    ....
    u64 *second;  // align to second CL after the first one
    ....
};

W kodzie można je zadeklarować używając rozszerzeń GCC takich jak:

__attribute__((aligned(CACHE_LINE_BYTES)))

Ale nadal chcesz się upewnić, że jest to egzekwowane w runtime.

ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);

To zadziała dla pojedynczej struktury, ale trudno byłoby pokryć wiele struktur, każda z nich ma inną nazwę Członka do wyrównania. Najprawdopodobniej otrzymasz kod jak poniżej, gdzie musisz znaleźć nazwy pierwszego członu każdej struktury:

assert (offsetof (one_struct,     <name_of_first_member>) == 0);
assert (offsetof (one_struct,     <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);

Zamiast iść w ten sposób, Można zadeklarować tablicę o zerowej długości w strukturze działającą jako nazwana Etykieta o spójnej nazwie, ale nie zajmuje żadnej spacji.

#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
    CACHE_LINE_ALIGN_MARK (cacheline0);
    u32 first; // align to CL
    u32 data;
    ....
    CACHE_LINE_ALIGN_MARK (cacheline1);
    u64 *second;  // align to second CL after the first one
    ....
};

Wtedy kod twierdzenia runtime byłby znacznie łatwiejsze w utrzymaniu:

assert (offsetof (one_struct,     cacheline0) == 0);
assert (offsetof (one_struct,     cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);
 1
Author: Wei Shen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-06 22:44:30