Was ist die Notwendigkeit eines Arrays mit Nullelementen?

Lesezeit: 9 Minuten

Benutzeravatar von Jeegar Patel
Jeegar Patel

Im Linux-Kernel-Code habe ich folgende Sache gefunden, die ich nicht verstehen kann.

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

Der Code ist hier: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

Was ist die Notwendigkeit und der Zweck eines Arrays von Daten mit null Elementen?

  • Ich bin mir nicht sicher, ob es entweder ein Array mit Nulllänge oder ein struct-hack-Tag geben sollte …

    – Hippiepfad

    1. Februar 2013 um 11:42 Uhr


  • @hippietrail, denn wenn jemand fragt, was diese Struktur ist, weiß er oft nicht, dass sie als “flexibles Array-Mitglied” bezeichnet wird. Wenn sie es getan hätten, hätten sie ihre Antwort leicht finden können. Da dies nicht der Fall ist, können sie die Frage nicht als solche markieren. Deshalb haben wir kein solches Tag.

    – Shahbaz

    1. Februar 2013 um 12:34 Uhr


  • Stimmen Sie für die Wiedereröffnung ab. Ich stimme zu, dass dies kein Duplikat war, da keiner der anderen Beiträge die Kombination eines nicht standardmäßigen “Struktur-Hacks” mit einer Länge von Null und dem wohldefinierten C99-Feature Flexible Array Member anspricht. Ich denke auch, dass es für die C-Programmiergemeinschaft immer von Vorteil ist, etwas Licht in den obskuren Code des Linux-Kernels zu bringen. Vor allem, weil viele Leute aus unbekannten Gründen den Eindruck haben, dass der Linux-Kernel eine Art hochmoderner C-Code ist. In Wirklichkeit ist es ein schreckliches Durcheinander, das mit nicht standardmäßigen Exploits überflutet ist, die niemals als ein C-Kanon angesehen werden sollten.

    – Ludin

    1. Februar 2013 um 13:01 Uhr


  • Kein Duplikat – ist nicht das erste Mal, dass ich jemanden sehe, der eine Frage unnötig schließt. Ich denke auch, dass diese Frage zur SO-Wissensbasis beiträgt.

    – Aniket Inge

    1. Februar 2013 um 13:15 Uhr


  • Mögliches Duplikat von Was passiert, wenn ich in C/C++ ein Array der Größe 0 definiere?

    – Jim fiel

    14. Mai 2018 um 13:31 Uhr

Benutzeravatar von Shahbaz
Shahbaz

Dies ist eine Möglichkeit, variable Datengrößen zu haben, ohne anrufen zu müssen malloc (kmalloc in diesem Fall) zweimal. Sie würden es wie folgt verwenden:

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

Dies war früher nicht Standard und wurde als Hack angesehen (wie Aniket sagte), aber es war so standardisiert in C99. Das Standardformat dafür ist jetzt:

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

Beachten Sie, dass Sie keine Größe für die angeben data aufstellen. Beachten Sie auch, dass diese spezielle Variable nur am Ende der Struktur stehen kann.


In C99 wird diese Angelegenheit in 6.7.2.1.16 (Hervorhebung von mir) erklärt:

Als Sonderfall kann das letzte Element einer Struktur mit mehr als einem benannten Element einen unvollständigen Array-Typ haben; Dies wird als flexibles Array-Member bezeichnet. In den meisten Situationen wird das flexible Arraymitglied ignoriert. Insbesondere ist die Größe der Struktur so, als ob das flexible Anordnungselement weggelassen worden wäre, außer dass es mehr nachlaufende Polsterung aufweisen kann, als das Weglassen implizieren würde. Wenn jedoch eine . (oder ->) Operator einen linken Operanden hat, der (ein Zeiger auf) eine Struktur mit einem flexiblen Array-Mitglied ist, und der rechte Operand dieses Mitglied benennt, verhält er sich so, als ob dieses Mitglied durch das längste Array (mit demselben Elementtyp) ersetzt worden wäre ), die die Struktur nicht größer machen würde als das Objekt, auf das zugegriffen wird; der Versatz des Arrays muss der des flexiblen Array-Elements bleiben, auch wenn dieser von dem des Ersatz-Arrays abweichen würde. Wenn dieses Array keine Elemente hätte, verhält es sich so, als hätte es ein Element, aber das Verhalten ist undefiniert, wenn versucht wird, auf dieses Element zuzugreifen oder einen Zeiger darüber hinaus zu generieren.

Oder anders gesagt, wenn Sie:

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

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

Sie können darauf zugreifen var->data mit Indizes drin [0, extra). Note that sizeof(struct something) will only give the size accounting for the other variables, i.e. gives data a size of 0.


It may be interesting also to note how the standard actually gives examples of mallocing such a construct (6.7.2.1.17):

struct s { int n; double d[];  };  int m = /* irgendein Wert */;  struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

Eine weitere interessante Anmerkung des Standards an derselben Stelle ist (Hervorhebung von mir):

Unter der Annahme, dass der Aufruf von malloc erfolgreich ist, verhält sich das Objekt, auf das p zeigt, für die meisten Zwecke so, als wäre p deklariert worden als:

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

(Es gibt Umstände, unter denen diese Äquivalenz gebrochen wird; insbesondere Die Offsets von Mitglied d sind möglicherweise nicht identisch).

  • Um es klarzustellen, der ursprüngliche Code in der Frage ist immer noch kein Standard in C99 (noch C11) und würde immer noch als Hack betrachtet werden. Die C99-Standardisierung muss die Array-Grenze weglassen.

    – MM

    6. Mai 2017 um 12:49 Uhr

  • Was ist [0, extra)?

    – S.S. Anne

    Dec 9, 2019 at 2:14

  • @JL2210, en.wikipedia.org/wiki/Interval_(mathematics)#Terminology

    – Shahbaz

    Dec 14, 2019 at 4:38


Aniket Inge's user avatar
Aniket Inge

This is a hack actually, for GCC (C90) in fact.

It’s also called a struct hack.

So the next time, I would say:

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

It will be equivalent to saying:

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

Und ich kann beliebig viele solcher Struct-Objekte erstellen.

Die Idee ist, am Ende der Struktur ein Array variabler Größe zuzulassen. Vermutlich, bts_action ist ein Datenpaket mit einem Header fester Größe (die type und size Felder) und variabler Größe data Mitglied. Indem es als Array der Länge 0 deklariert wird, kann es wie jedes andere Array indiziert werden. Sie würden dann a zuweisen bts_action struct von sagen wir 1024 Byte data Größe, so:

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

Siehe auch: http://c2.com/cgi/wiki?StructHack

  • @Aniket: Ich bin mir nicht ganz sicher, woher das kommt das Idee.

    – schau

    1. Februar 2013 um 17:57 Uhr

  • in C++ ja, in C nicht nötig.

    Benutzer339222

    8. Februar 2013 um 2:15 Uhr

  • @sheu, das kommt daher, dass dein Schreibstil malloc macht man sich mehrmals wiederholen und wenn überhaupt die Art von action ändert, müssen Sie es mehrmals korrigieren. Vergleichen Sie die folgenden beiden für sich selbst und Sie werden es wissen: struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing)); vs. struct some_thing *variable = malloc(10 * sizeof(*variable)); Der zweite ist kürzer, sauberer und deutlich einfacher zu wechseln.

    – Shahbaz

    12. Februar 2013 um 13:05 Uhr

Benutzeravatar von Lundin
Lundin

Der Code ist ungültig C (siehe hier). Der Linux-Kernel kümmert sich aus offensichtlichen Gründen nicht im Geringsten um Portabilität, also verwendet er viel Nicht-Standard-Code.

Was sie tun, ist eine GCC-Nicht-Standard-Erweiterung mit Array-Größe 0. Ein standardkonformes Programm hätte geschrieben u8 data[]; und es hätte dasselbe bedeutet. Die Autoren des Linux-Kernels lieben es offenbar, Dinge unnötig kompliziert und nicht standardisiert zu machen, wenn sich eine Möglichkeit dazu ergibt.

In älteren C-Standards wurde das Beenden einer Struktur mit einem leeren Array als “Struct-Hack” bezeichnet. Andere haben seinen Zweck bereits in anderen Antworten erklärt. Der Struct-Hack im C90-Standard war ein undefiniertes Verhalten und konnte Abstürze verursachen, hauptsächlich da ein C-Compiler beliebig viele Füllbytes am Ende der Struktur hinzufügen kann. Solche Füllbytes können mit den Daten kollidieren, die Sie am Ende der Struktur “hacken” wollten.

GCC hat schon früh eine nicht standardmäßige Erweiterung vorgenommen, um dies von einem undefinierten zu einem wohldefinierten Verhalten zu ändern. Der C99-Standard hat dieses Konzept dann adaptiert und jedes moderne C-Programm kann diese Funktion daher ohne Risiko nutzen. Es ist bekannt als flexibles Array-Mitglied in C99/C11.

Benutzeravatar von Wei Shen
Wei Shen

Eine weitere Verwendung eines Arrays der Länge Null ist die Verwendung als benanntes Label innerhalb einer Struktur, um die Kompilierungszeit-Struktur-Offset-Prüfung zu unterstützen.

Angenommen, Sie haben einige große Strukturdefinitionen (die sich über mehrere Cache-Zeilen erstrecken), bei denen Sie sicherstellen möchten, dass sie sowohl am Anfang als auch in der Mitte, wo sie die Grenze kreuzen, an der Grenze der Cache-Zeile ausgerichtet sind.

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

Im Code können Sie sie mit GCC-Erweiterungen deklarieren wie:

__attribute__((aligned(CACHE_LINE_BYTES)))

Aber Sie möchten trotzdem sicherstellen, dass dies zur Laufzeit erzwungen wird.

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

Dies würde für eine einzelne Struktur funktionieren, aber es wäre schwierig, viele Strukturen abzudecken, da jede einen anderen Mitgliedsnamen hat, der ausgerichtet werden muss. Sie würden höchstwahrscheinlich Code wie unten erhalten, in dem Sie die Namen des ersten Elements jeder Struktur finden müssen:

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

Anstatt diesen Weg zu gehen, können Sie ein Array der Länge Null in der Struktur deklarieren, das als benanntes Label mit einem konsistenten Namen fungiert, aber keinen Platz verbraucht.

#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
    ....
};

Dann wäre der Laufzeit-Assertion-Code viel einfacher zu warten:

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

  • Interessante Idee. Nur eine Anmerkung, dass Arrays der Länge 0 vom Standard nicht erlaubt sind, also ist dies eine Compiler-spezifische Sache. Außerdem könnte es eine gute Idee sein, gccs Definition des Verhaltens von Arrays der Länge 0 in einer Struct-Definition zu zitieren, zumindest um zu zeigen, ob vor oder nach der Deklaration Padding eingeführt werden kann.

    – Shahbaz

    21. November 2016 um 23:07 Uhr

  • Interessante Idee. Nur eine Anmerkung, dass Arrays der Länge 0 vom Standard nicht erlaubt sind, also ist dies eine Compiler-spezifische Sache. Außerdem könnte es eine gute Idee sein, gccs Definition des Verhaltens von Arrays der Länge 0 in einer Struct-Definition zu zitieren, zumindest um zu zeigen, ob vor oder nach der Deklaration Padding eingeführt werden kann.

    – Shahbaz

    21. November 2016 um 23:07 Uhr

1423200cookie-checkWas ist die Notwendigkeit eines Arrays mit Nullelementen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy