Ich habe kürzlich gelesen, dass die Verwendung flexibler Array-Mitglieder in C eine schlechte Software-Engineering-Praxis ist. Diese Aussage wurde jedoch durch kein Argument gestützt. Ist das eine akzeptierte Tatsache?
( Flexible Array- Elemente sind eine in C99 eingeführte C-Funktion, mit der das letzte Element als Array mit nicht angegebener Größe deklariert werden kann. Zum Beispiel :)
struct header {
size_t len;
unsigned char data[];
};
quelle
while
Dann solltest du wahrscheinlich verwenden .goto
ist nicht für Schleifen.BITTE LESEN SIE DIE KOMMENTARE UNTER DIESER ANTWORT SORGFÄLTIG DURCH
Mit fortschreitender C-Standardisierung gibt es keinen Grund mehr, [1] zu verwenden.
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 bereits C99 als Voraussetzung für die Erstellung Ihres Codes aus einem anderen Grund haben oder auf einen bestimmten Compiler abzielen, sehe ich keinen Schaden.
quelle
n-1
berücksichtigt die zusätzliche Zuordnung aufgrund der Ausrichtung nicht genau: Auf den meisten 32-Bit-Computern beträgt sizeof (Strukturheader) 8 (um ein Vielfaches von 4 zu bleiben, da es ein 32- Bitfeld, das eine solche Ausrichtung bevorzugt / erfordert). Die "bessere" Version ist:malloc(offsetof(struct header, data) + n)
unsigned char data[1]
nicht portierbar, da((header*)ptr)->data + 2
- selbst wenn genügend Speicherplatz zugewiesen wurde - ein Zeiger erstellt wird, der außerhalb des Array-Objekts der Länge 1 zeigt (und nicht des Sentinel-Objekts nach dem Ende). Gemäß C99 6.5.6p8: "Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, darf die Auswertung keinen Überlauf erzeugen. Andernfalls ist das Verhalten undefiniert " (Betonung hinzugefügt). Flexible Arrays (6.7.2.2p16) verhalten sich wie ein Array, das den zugewiesenen Speicherplatz ausfüllt, um UB hier nicht zu treffen.[1]
gezeigt wurde GCC verursachen falschen Code zu generieren: lkml.org/lkml/2015/2/18/407Du 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 unter Umständen, in denen Sie wirklich eine Reihe unbekannter Größe benötigen. Stellen Sie sicher, dass Sie den Speicher korrekt verwenden 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 dann die Daten nach Bedarf auf die richtige Größe umverteilen.
struct header { size_t len; unsigned char *data; };
Wenn Sie nach C ++ fragen, ist dies natürlich eine schlechte Praxis. Dann würden Sie normalerweise stattdessen STL-Vektoren verwenden.
quelle
Nein, die Verwendung flexibler Array-Elemente in C ist keine schlechte Praxis.
Diese Sprachfunktion wurde erstmals in ISO C99, 6.7.2.1 (16) standardisiert. Für die aktuelle Norm ISO C11 ist sie in Abschnitt 6.7.2.1 (18) angegeben.
Sie können sie folgendermaßen 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 es folgendermaßen zuordnen:
Header *h = malloc(sizeof *h + n * sizeof h->v[0]);
Beachten Sie, dass
sizeof(Header)
eventuelle Auffüllbytes enthalten sind. 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 einem flexiblen Array-Element reduziert die Anzahl der Zuweisungen für dieses um 1/2, dh anstelle von 2 Zuweisungen für ein Strukturobjekt benötigen Sie nur 1. Dies bedeutet weniger Aufwand und weniger Speicherplatz für den Buchhaltungsaufwand für den Speicherzuweiser. Außerdem speichern Sie den Speicher für einen zusätzlichen Zeiger. Wenn Sie also eine große Anzahl solcher Strukturinstanzen zuweisen müssen, verbessern Sie messbar die Laufzeit und die Speichernutzung Ihres Programms (um einen konstanten Faktor).
Im Gegensatz dazu ist die Verwendung nicht standardisierter Konstrukte für flexible Array-Mitglieder, die ein undefiniertes Verhalten ergeben (z. B. wie in
long v[0];
oderlong v[1];
), offensichtlich eine schlechte Praxis. Daher sollte dies wie jedes undefinierte Verhalten vermieden werden.Seit der Veröffentlichung von ISO C99 im Jahr 1999, vor fast 20 Jahren, ist das Streben nach ISO C89-Kompatibilität ein schwaches Argument.
quelle
Ich habe so etwas gesehen: von der C-Schnittstelle und der 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 Mitglied sein.
quelle
data
dass es nicht das letzte Mitglied sein muss, sondern dass jedes Mal, wenn es verwendet wird, eine zusätzliche Dereferenzierung auftrittdata
. Flexible Arrays ersetzen diese Dereferenzierung durch einen konstanten Versatz zum Hauptstrukturzeiger, der auf einigen besonders gängigen Maschinen kostenlos und an anderer Stelle billig ist.unsigned char *
,p->data = (unsigned char*) (p + 1 )
ist in Ordnung. Doch mitdouble complex *
,p->data = (double complex *) (p + 1 )
können Ausrichtungsprobleme verursachen.Als Randnotiz sollte eine solche Struktur aus Gründen der C89-Kompatibilität 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));
Wenn Sie FLEXIBLE_SIZE auf SIZE_MAX setzen, wird fast sichergestellt, dass dies fehlschlägt:
struct header *my_header = malloc(sizeof *my_header);
quelle
[1]
für C89-Kompatibilität, wenn es überhaupt benötigt wird ...Es gibt einige Nachteile in Bezug darauf, wie Strukturen manchmal verwendet werden, und es kann gefährlich sein, wenn Sie die Auswirkungen nicht durchdenken.
Wenn Sie für Ihr Beispiel eine Funktion starten:
void test(void) { struct header; char *p = &header.data[0]; ... }
Dann sind die Ergebnisse undefiniert (da niemals Speicher für Daten zugewiesen wurde). Dies ist Ihnen normalerweise bekannt, aber es gibt Fälle, in denen C-Programmierer wahrscheinlich daran gewöhnt sind, Wertesemantik für Strukturen zu verwenden, die auf verschiedene andere Arten zusammenbricht.
Zum Beispiel, wenn ich definiere:
struct header2 { int len; char data[MAXLEN]; /* MAXLEN some appropriately large number */ }
Dann kann ich zwei Instanzen einfach durch Zuweisung kopieren, dh:
struct header2 inst1 = inst2;
Oder wenn sie als Zeiger definiert sind:
struct header2 *inst1 = *inst2;
Dies funktioniert jedoch nicht, da das variable Array
data
nicht kopiert wird. Was Sie wollen, ist, die Größe der Struktur dynamisch zu ändern und mitmemcpy
oder einer Entsprechung über das Array zu kopieren .Ebenso funktioniert das Schreiben einer Funktion, die eine Struktur akzeptiert, nicht, da Argumente in Funktionsaufrufen wiederum nach Wert kopiert werden und das, was Sie erhalten, wahrscheinlich nur das erste Element Ihres Arrays ist
data
.Dies macht es nicht zu einer schlechten Idee, aber Sie müssen bedenken, diese Strukturen immer dynamisch zuzuweisen und sie nur als Zeiger weiterzugeben.
quelle