Ist die Verwendung flexibler Array-Mitglieder in C eine schlechte Praxis?

Lesezeit: 10 Minuten

Ich habe kürzlich gelesen, dass die Verwendung flexibler Array-Mitglieder in C eine schlechte Software-Engineering-Praxis war. Diese Aussage wurde jedoch nicht durch Argumente gestützt. Ist das eine akzeptierte Tatsache?

(Flexible Array-Mitglieder sind ein in C99 eingeführtes C-Feature, mit dem man das letzte Element als Array unspezifizierter Größe deklarieren kann. Zum Beispiel: )

struct header {
    size_t len;
    unsigned char data[];
};

Benutzeravatar von Airsource Ltd
Airsource Ltd

Es ist eine anerkannte “Tatsache”, dass die Verwendung von goto eine schlechte Software-Engineering-Praxis ist. Das macht es nicht wahr. Es gibt Zeiten, in denen goto nützlich ist, insbesondere beim Aufräumen und beim Portieren von Assembler.

Flexible Array-Mitglieder scheinen mir spontan einen Hauptzweck zu haben, der Legacy-Datenformate wie Fenstervorlagenformate auf RiscOS abbildet. Sie wären dafür vor etwa 15 Jahren äußerst nützlich gewesen, und ich bin sicher, dass es immer noch Leute da draußen gibt, die sich mit solchen Dingen befassen, die sie nützlich finden würden.

Wenn die Verwendung flexibler Array-Mitglieder eine schlechte Praxis ist, dann schlage ich vor, dass wir alle dies den Autoren der C99-Spezifikation mitteilen. Ich vermute, dass sie eine andere Antwort haben könnten.

  • goto ist auch nützlich, wenn wir eine rekursive Implementierung eines Algorithmus mithilfe einer nicht rekursiven Implementierung implementieren möchten, in Fällen, in denen die Rekursion einen zusätzlichen Aufwand für den Compiler verursachen könnte.

    – pranavk

    16. Oktober 2012 um 18:12 Uhr

  • @pranavk Sie sollten wahrscheinlich verwenden whiledann.

    – yyny

    17. Oktober 2016 um 22:35 Uhr

  • Netzwerkprogrammierung ist eine andere, Sie haben den Header als Struktur und das Paket (oder wie es in der Ebene genannt wird, in der Sie sich befinden) als flexibles Array. Wenn Sie die nächste Schicht aufrufen, entfernen Sie den Header und geben das Paket weiter. Tun Sie dies für jede Schicht im Netzwerkstapel. (Sie legen die Daten von der unteren Ebene wiederbelebt aus der unteren Ebene in eine Struktur für die Ebene, in der Sie sich befinden.)

    – fhtuft

    9. Januar 2017 um 9:55 Uhr

  • @pranavk goto ist nicht für Schleifen.

    – Константин Ван

    10. Mai 2020 um 8:17 Uhr

  • “Es gibt Zeiten, in denen goto nützlich ist” Sehen Sie, das ist der Grund, warum ich manchmal schaudere, wenn ich denke, dass ein Kind, das gerade Programmieren lernt, auf StackOverflow zurückgreifen wird, um Best Practices zu lernen.

    – Martin

    29. Juni 2021 um 18:38 Uhr


Benutzeravatar von maxschlepzig
maxschlepzig

Nein, verwenden flexible Array-Mitglieder in C ist keine schlechte Praxis.

Diese Sprachfunktion wurde zuerst in ISO C99, 6.7.2.1 (16) standardisiert. In der folgenden Überarbeitung, ISO C11, ist es in Abschnitt 6.7.2.1 (18) spezifiziert.

Sie können sie wie folgt verwenden:

struct Header {
    size_t d;
    long v[];
};
typedef struct Header Header;
size_t n = 123; // can dynamically change during program execution
// ...
Header *h = malloc(sizeof(Header) + sizeof(long[n]));
h->n = n;

Alternativ können Sie auch so zuweisen:

Header *h = malloc(sizeof *h + n * sizeof h->v[0]);

Beachten Sie, dass sizeof(Header) enthält eventuelle Füllbytes, daher ist die folgende Zuordnung falsch und kann zu einem Pufferüberlauf führen:

Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid!

Eine Struktur mit flexiblen Array-Mitgliedern reduziert die Anzahl der Zuweisungen dafür um 1/2, dh statt 2 Zuweisungen für ein Strukturobjekt benötigen Sie nur 1. Das bedeutet weniger Aufwand und weniger Speicherverbrauch durch Speicherallokator-Buchhaltungsaufwand. Außerdem spart man sich den Speicherplatz für einen zusätzlichen Zeiger. Wenn Sie also eine große Anzahl solcher Struct-Instanzen zuweisen müssen, verbessern Sie die Laufzeit und den Speicherverbrauch Ihres Programms messbar (um einen konstanten Faktor).

Im Gegensatz dazu kann die Verwendung nicht standardisierter Konstrukte für flexible Array-Mitglieder, die zu undefiniertem Verhalten führen (z. B. wie in long v[0]; oder long v[1];) ist offensichtlich schlechte Praxis. Daher sollte dies wie jedes undefinierte Verhalten vermieden werden.

Seit der Veröffentlichung von ISO C99 im Jahr 1999, also vor mehr als 20 Jahren, ist das Streben nach ISO C89-Kompatibilität ein schwaches Argument.

Benutzeravatar von Remo.D
Remo.D

BITTE LESEN SIE SORGFÄLTIG DIE KOMMENTARE UNTER DIESER ANTWORT

Da die C-Standardisierung voranschreitet, gibt es keinen Grund für die Verwendung [1] mehr.

Der Grund, warum ich es nicht tun würde, ist, dass es sich nicht lohnt, Ihren Code an C99 zu binden, nur um diese Funktion zu verwenden.

Der Punkt ist, dass Sie immer die folgende Redewendung verwenden können:

struct header {
  size_t len;
  unsigned char data[1];
};

Das ist voll tragbar. Dann können Sie die 1 berücksichtigen, wenn Sie den Speicher für n Elemente im Array zuweisen data :

ptr = malloc(sizeof(struct header) + (n-1));

Wenn Sie aus irgendeinem anderen Grund bereits C99 als Voraussetzung zum Erstellen Ihres Codes haben oder auf einen bestimmten Compiler abzielen, sehe ich keinen Schaden.

  • Vielen Dank. Ich habe das n-1 verlassen, da es möglicherweise nicht als Zeichenfolge verwendet wird.

    – Remo.D

    29. Oktober 2008 um 14:40 Uhr

  • Das „folgende Idiom“ ist nicht vollständig portierbar, weshalb dem C99-Standard flexible Array-Mitglieder hinzugefügt wurden.

    – Jonathan Leffler

    1. November 2008 um 0:50 Uhr

  • @Remo.D: Nebensache: die n-1 berücksichtigt die zusätzliche Zuweisung aufgrund der Ausrichtung nicht genau: Auf den meisten 32-Bit-Computern ist sizeof(struct header) 8 (um ein Vielfaches von 4 zu bleiben, da es ein 32-Bit-Feld hat, das eine solche Ausrichtung bevorzugt/erfordert). . Die “bessere” Version ist: malloc(offsetof(struct header, data) + n)

    – Tom Lauch

    26. August 2012 um 19:54 Uhr

  • In C99 mit unsigned char data[1] ist nicht tragbar, weil ((header*)ptr)->data + 2 — selbst wenn genügend Platz zugewiesen wurde — erstellt einen Zeiger, der außerhalb des Array-Objekts der Länge 1 zeigt (und nicht auf das Sentinel-Objekt hinter dem Ende). Aber gemäß C99 6.5.6p8: „Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Array-Objekts oder eins nach dem letzten Element des Array-Objekts zeigen, darf die Auswertung keinen Überlauf erzeugen; andernfalls ist das Verhalten undefiniert” (Hervorhebung hinzugefügt). Flexible Arrays (6.7.2.2p16) wirken wie ein Array, das den zugewiesenen Platz ausfüllt, um UB hier nicht zu treffen.

    – Jeff Walden

    21. Juni 2013 um 4:25 Uhr

  • *WARNUNG: Verwenden [1] hat sich gezeigt, dass GCC falschen Code generiert: lkml.org/lkml/2015/2/18/407

    – Jonathon Reinhart

    20. Februar 2015 um 19:49 Uhr

Du meintest…

struct header
{
 size_t len;
 unsigned char data[];
}; 

In C ist das eine gängige Redewendung. Ich denke, viele Compiler akzeptieren auch:

  unsigned char data[0];

Ja, es ist gefährlich, aber andererseits ist es wirklich nicht gefährlicher als normale C-Arrays – dh SEHR gefährlich 😉 . Verwenden Sie es mit Vorsicht und nur in Situationen, in denen Sie wirklich ein Array unbekannter Größe benötigen. Stellen Sie sicher, dass Sie den Speicher korrekt mallocieren und freigeben, indem Sie Folgendes verwenden:

  foo = malloc(sizeof(header) + N * sizeof(data[0]));
  foo->len = N;

Eine Alternative besteht darin, Daten nur als Zeiger auf die Elemente zu verwenden. Sie können die Daten dann je nach Bedarf in der richtigen Größe neu zuordnen ().

  struct header
    {
     size_t len;
     unsigned char *data;
    }; 

Wenn Sie nach C++ fragen würden, wäre beides natürlich eine schlechte Übung. Dann würden Sie normalerweise stattdessen STL-Vektoren verwenden.

Ich habe so etwas gesehen: von C-Schnittstelle und Implementierung.

  struct header {
    size_t len;
    unsigned char *data;
};

   struct header *p;
   p = malloc(sizeof(*p) + len + 1 );
   p->data = (unsigned char*) (p + 1 );  // memory after p is mine! 

Hinweis: Daten müssen nicht das letzte Element sein.

  • Dies hat allerdings den Vorteil, dass data muss nicht das letzte Mitglied sein, aber es führt auch jedes Mal zu einer zusätzlichen Dereferenzierung data wird genutzt. Flexible Arrays ersetzen diese Dereferenzierung durch einen konstanten Offset vom Hauptstrukturzeiger, der auf einigen besonders verbreiteten Maschinen kostenlos und anderswo billig ist.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    6. August 2010 um 15:39 Uhr

  • @R.. Obwohl, unter Berücksichtigung der Zieladresse Notwendig das Byte direkt nach dem Zeiger, ist es ungefähr zu 100 % garantiert, dass es sich bereits im L1-Cache befindet, was der gesamten Dereferenzierung etwa einen halben Zyklus an Overhead verleiht. Der Punkt steht jedoch, dass flexible Arrays hier eine bessere Idee sind.

    – pmttavara

    1. April 2018 um 11:28 Uhr

  • Mit unsigned char *, p->data = (unsigned char*) (p + 1 ) ist in Ordnung. Doch mit double complex *, p->data = (double complex *) (p + 1 ) kann Ausrichtungsprobleme verursachen.

    – chux – Wiedereinsetzung von Monica

    16. Mai 2018 um 17:21 Uhr

  • Diese Antwort ist technisch irrelevant, da sie macht was anderes (Es legt die Daten im Speicher anders an). Obwohl das beschriebene Muster oft nützlich ist, bedeutet das nicht, dass es das andere ersetzen kann.

    – Donal Fellows

    28. April 2020 um 11:15 Uhr

Als Randnotiz sollte für die C89-Kompatibilität eine solche Struktur wie folgt zugewiesen werden:

struct header *my_header
  = malloc(offsetof(struct header, data) + n * sizeof my_header->data);

Oder mit Makros:

#define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */
#define SIZEOF_FLEXIBLE(type, member, length) \
  ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] )

struct header {
  size_t len;
  unsigned char data[FLEXIBLE_SIZE];
};

...

size_t n = 123;
struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n));

Das Setzen von FLEXIBLE_SIZE auf SIZE_MAX stellt fast sicher, dass dies fehlschlägt:

struct header *my_header = malloc(sizeof *my_header);

  • Dies hat allerdings den Vorteil, dass data muss nicht das letzte Mitglied sein, aber es führt auch jedes Mal zu einer zusätzlichen Dereferenzierung data wird genutzt. Flexible Arrays ersetzen diese Dereferenzierung durch einen konstanten Offset vom Hauptstrukturzeiger, der auf einigen besonders verbreiteten Maschinen kostenlos und anderswo billig ist.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    6. August 2010 um 15:39 Uhr

  • @R.. Obwohl, unter Berücksichtigung der Zieladresse Notwendig das Byte direkt nach dem Zeiger, ist es ungefähr zu 100 % garantiert, dass es sich bereits im L1-Cache befindet, was der gesamten Dereferenzierung etwa einen halben Zyklus an Overhead verleiht. Der Punkt steht jedoch, dass flexible Arrays hier eine bessere Idee sind.

    – pmttavara

    1. April 2018 um 11:28 Uhr

  • Mit unsigned char *, p->data = (unsigned char*) (p + 1 ) ist in Ordnung. Doch mit double complex *, p->data = (double complex *) (p + 1 ) kann Ausrichtungsprobleme verursachen.

    – chux – Wiedereinsetzung von Monica

    16. Mai 2018 um 17:21 Uhr

  • Diese Antwort ist technisch irrelevant, da sie macht was anderes (Es legt die Daten im Speicher anders an). Obwohl das beschriebene Muster oft nützlich ist, bedeutet das nicht, dass es das andere ersetzen kann.

    – Donal Fellows

    28. April 2020 um 11:15 Uhr

Benutzeravatar von Chef Gladiator
Koch Gladiator

Es gibt einige Nachteile in Bezug darauf, wie Strukturen manchmal verwendet werden, und es kann gefährlich sein, wenn Sie die Auswirkungen nicht durchdenken.

Für Ihr Beispiel, wenn Sie eine Funktion starten:

void test(void) {
  struct header;
  char *p = &header.data[0];

  ...
}

Dann sind die Ergebnisse undefiniert (da den Daten nie Speicherplatz zugewiesen wurde). Dies ist etwas, das Ihnen normalerweise bewusst ist, aber es gibt Fälle, in denen C-Programmierer wahrscheinlich daran gewöhnt sind, Wertesemantik für Strukturen verwenden zu können, die auf verschiedene andere Arten zusammenbricht.

Wenn ich zum Beispiel definiere:

struct header2 {
  int len;
  char data[MAXLEN]; /* MAXLEN some appropriately large number */
}

Dann kann ich einfach per Zuweisung zwei Instanzen kopieren, also:

struct header2 inst1 = inst2;

Oder wenn sie als Zeiger definiert sind:

struct header2 *inst1 = *inst2;

Dies funktioniert jedoch nicht für flexible Array-Mitglieder, da deren Inhalt nicht kopiert wird. Was Sie wollen, ist, die Größe der Struktur dynamisch zu mallocieren und über das Array zu kopieren memcpy oder gleichwertig.

struct header3 {
  int len;
  char data[]; /* flexible array member */
}

Ebenso schreibt man eine Funktion, die a akzeptiert struct header3 wird nicht funktionieren, da Argumente in Funktionsaufrufen wiederum nach Wert kopiert werden und Sie daher wahrscheinlich nur das erste Element Ihres flexiblen Array-Mitglieds erhalten.

 void not_good ( struct header3 ) ;

Dies macht die Verwendung nicht zu einer schlechten Idee, aber Sie müssen daran denken, diese Strukturen immer dynamisch zuzuweisen und sie nur als Zeiger weiterzugeben.

 void good ( struct header3 * ) ;

1418040cookie-checkIst die Verwendung flexibler Array-Mitglieder in C eine schlechte Praxis?

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

Privacy policy