Ich arbeite an der Umgestaltung von altem Code und habe einige Strukturen gefunden, die Arrays der Länge Null enthalten (unten). Warnungen werden natürlich durch Pragma unterdrückt, aber ich habe es versäumt, durch “neue” Strukturen zu erstellen, die solche Strukturen enthalten (Fehler 2233). Array ‘byData’ als Zeiger verwendet, aber warum nicht stattdessen Zeiger verwenden? oder Array der Länge 1? Und natürlich wurden keine Kommentare hinzugefügt, damit ich den Prozess genießen könnte … Gibt es Gründe, so etwas zu verwenden? Irgendwelche Ratschläge beim Refactoring dieser?
struct someData
{
int nData;
BYTE byData[0];
}
NB: Es ist C++, Windows XP, VS 2003
Ja, das ist ein C-Hack.
So erstellen Sie ein Array beliebiger Länge:
struct someData* mallocSomeData(int size)
{
struct someData* result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE));
if (result)
{ result->nData = size;
}
return result;
}
Jetzt haben Sie ein Objekt von someData mit einem Array einer bestimmten Länge.
Es gibt leider mehrere Gründe, warum Sie ein Array der Länge Null am Ende einer Struktur deklarieren würden. Es gibt Ihnen im Wesentlichen die Möglichkeit, eine Struktur mit variabler Länge von einer API zurückzugeben.
Raymond Chen hat einen ausgezeichneten Blogbeitrag zu diesem Thema verfasst. Ich schlage vor, dass Sie sich diesen Beitrag ansehen, da er wahrscheinlich die gewünschte Antwort enthält.
Beachten Sie in seinem Beitrag, dass es sich um Arrays der Größe 1 anstelle von 0 handelt. Dies ist der Fall, weil Arrays der Länge Null ein neuerer Eintrag in die Standards sind. Sein Beitrag sollte immer noch auf Ihr Problem zutreffen.
http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx
BEARBEITEN
Hinweis: Obwohl Raymonds Beitrag besagt, dass Arrays der Länge 0 in C99 legal sind, sind sie in C99 tatsächlich immer noch nicht legal. Anstelle eines Arrays der Länge 0 sollten Sie hier ein Array der Länge 1 verwenden
Dies ist ein alter C-Hack, um Arrays mit flexibler Größe zu ermöglichen.
Im C99-Standard ist dies nicht erforderlich, da er die arr unterstützt[] Syntax.
Ihre Intuition zu “Warum nicht ein Array der Größe 1 verwenden” ist genau richtig.
Der Code macht den “C-Struktur-Hack” falsch, da Deklarationen von Arrays der Länge Null eine Einschränkungsverletzung darstellen. Dies bedeutet, dass ein Compiler Ihren Hack direkt zur Kompilierzeit mit einer Diagnosemeldung ablehnen kann, die die Übersetzung stoppt.
Wenn wir einen Hack ausführen wollen, müssen wir ihn am Compiler vorbeischleichen.
Der richtige Weg, den “C-Struktur-Hack” durchzuführen (der mit C-Dialekten kompatibel ist, die bis 1989 ANSI C und wahrscheinlich viel früher zurückreichen), besteht darin, ein vollkommen gültiges Array der Größe 1 zu verwenden:
struct someData
{
int nData;
unsigned char byData[1];
}
Außerdem statt sizeof struct someData
die Größe des Teils vor byData
wird berechnet mit:
offsetof(struct someData, byData);
A zuzuweisen struct someData
mit Platz für 42 Bytes in byData
würden wir dann verwenden:
struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);
Beachten Sie, dass dies offsetof
Die Berechnung ist tatsächlich die richtige Berechnung, selbst wenn die Array-Größe Null ist. Siehst du, sizeof
die gesamte Struktur kann eine Polsterung enthalten. Wenn wir zum Beispiel so etwas haben:
struct hack {
unsigned long ul;
char c;
char foo[0]; /* assuming our compiler accepts this nonsense */
};
Die Größe von struct hack
ist wegen der evtl. zur Ausrichtung gepolstert ul
Mitglied. Wenn unsigned long
vier Bytes breit ist, dann durchaus möglich sizeof (struct hack)
ist 8, wohingegen offsetof(struct hack, foo)
ist mit ziemlicher Sicherheit 5. Die offsetof
Methode ist der Weg, um die genaue Größe des vorhergehenden Teils der Struktur direkt vor dem Array zu erhalten.
Das wäre also der Weg, den Code umzugestalten: ihn an den klassischen, hochportablen Struct-Hack anzupassen.
Warum nicht einen Zeiger verwenden? Weil ein Zeiger zusätzlichen Platz belegt und initialisiert werden muss.
Es gibt noch andere gute Gründe, einen Zeiger nicht zu verwenden, nämlich dass ein Zeiger einen Adressraum benötigt, um sinnvoll zu sein. Der Struct-Hack ist externalisierbar: Das heißt, es gibt Situationen, in denen ein solches Layout externen Speichern wie Dateibereichen, Paketen oder gemeinsam genutztem Speicher entspricht, in denen Sie keine Zeiger haben möchten, weil sie nicht aussagekräftig sind.
Vor einigen Jahren habe ich den Struct-Hack in einer Shared-Memory-Message-Passing-Schnittstelle zwischen Kernel und Userspace verwendet. Ich wollte dort keine Zeiger, weil sie nur für den ursprünglichen Adressraum des Prozesses, der eine Nachricht generiert, von Bedeutung gewesen wären. Der Kernel-Teil der Software hatte einen Blick auf den Speicher, indem er ein eigenes Mapping an einer anderen Adresse verwendete, und so basierte alles auf Offset-Berechnungen.
Es lohnt sich, IMO auf den besten Weg zur Größenberechnung hinzuweisen, der im oben verlinkten Artikel von Raymond Chen verwendet wird.
struct foo
{
size_t count;
int data[1];
}
size_t foo_size_from_count(size_t count)
{
return offsetof(foo, data[count]);
}
Der Versatz des ersten Eintrags vom Ende der gewünschten Zuordnung ist auch die Größe der gewünschten Zuordnung. IMO ist es eine äußerst elegante Art, die Größenberechnung durchzuführen. Es spielt keine Rolle, welchen Elementtyp das Array mit variabler Größe hat. Der offsetof (oder FIELD_OFFSET oder UFIELD_OFFSET in Windows) wird immer gleich geschrieben. Keine sizeof()-Ausdrücke, die versehentlich durcheinandergebracht werden könnten.
Dies ist der in Frage 2.6 beschriebene “struct hack”. comp.lang.c FAQ. Dennis Ritchie nannte es “ungerechtfertigte Kumpanei mit der C-Implementierung”. C99 führte eine neue Sprachfunktion ein, das “flexible Array Member”, um den Struct-Hack zu ersetzen. Sogar der Compiler von Microsoft, der für seine fehlende C99-Unterstützung bekannt ist, unterstützt flexible Array-Member.
– Keith Thompson
11. September 2012 um 18:37 Uhr