Was passiert, wenn ich in C / C ++ ein Array der Größe 0 definiere?

127

Nur neugierig, was passiert eigentlich, wenn ich ein Array mit der Länge Null int array[0];im Code definiere? GCC beschwert sich überhaupt nicht.

Beispielprogramm

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

Klärung

Ich versuche tatsächlich herauszufinden, ob Arrays mit der Länge Null, die auf diese Weise initialisiert wurden, optimiert sind oder nicht, anstatt wie in Darhazers Kommentaren auf die variable Länge hingewiesen zu werden.

Dies liegt daran, dass ich Code in die Wildnis freigeben muss, also versuche ich herauszufinden, ob ich Fälle behandeln muss, in denen der SIZEdefiniert ist 0, was in einem Code mit einem statisch definierten Code der Fall istint array[SIZE];

Ich war tatsächlich überrascht, dass sich GCC nicht beschwert, was zu meiner Frage führte. Aufgrund der Antworten, die ich erhalten habe, glaube ich, dass das Fehlen einer Warnung hauptsächlich auf die Unterstützung von altem Code zurückzuführen ist, der nicht mit der neuen [] -Syntax aktualisiert wurde.

Da ich mich hauptsächlich über den Fehler gewundert habe, markiere ich Lundins Antwort als richtig (Nawaz war die erste, aber nicht so vollständig) - die anderen wiesen darauf hin, dass die tatsächliche Verwendung für schwanzgepolsterte Strukturen zwar relevant ist, aber nicht. genau das, wonach ich gesucht habe.

Alex Koay
quelle
51
@AlexanderCorwin: Leider ist es in C ++ mit undefiniertem Verhalten, nicht standardmäßigen Erweiterungen und anderen Anomalien oft kein Weg zum Wissen, etwas selbst auszuprobieren.
Benjamin Lindley
5
@JustinKirk Das hat mich auch gefangen, als ich getestet und gesehen habe, dass es funktioniert hat. Und aufgrund der Kritik, die ich in meinem Beitrag erhalten habe, habe ich gelernt, dass das Testen und Sehen, dass es funktioniert, nicht bedeutet, dass es gültig und legal ist. Ein Selbsttest ist also manchmal nicht gültig.
StormByte
2
@JustinKirk, siehe Matthieus Antwort für ein Beispiel, wo Sie es verwenden würden. Dies kann auch in einer Vorlage nützlich sein, in der die Arraygröße ein Vorlagenparameter ist. Das Beispiel in der Frage ist offensichtlich aus dem Zusammenhang geraten.
Mark Ransom
2
@ JustinKirk: Was ist der Zweck []in Python oder sogar ""in C? Manchmal haben Sie eine Funktion oder ein Makro, für das ein Array erforderlich ist, aber Sie müssen keine Daten einfügen.
Dan04
15
Was ist "C / C ++"? Dies sind zwei getrennte Sprachen
Leichtigkeitsrennen im Orbit

Antworten:

86

Ein Array kann keine Größe von Null haben.

ISO 9899: 2011 6.7.6.2:

Wenn der Ausdruck ein konstanter Ausdruck ist, muss er einen Wert größer als Null haben.

Der obige Text gilt sowohl für ein einfaches Array (Absatz 1). Bei einem VLA (Array mit variabler Länge) ist das Verhalten undefiniert, wenn der Wert des Ausdrucks kleiner oder gleich Null ist (Absatz 5). Dies ist normativer Text im C-Standard. Ein Compiler darf es nicht anders implementieren.

gcc -std=c99 -pedantic gibt eine Warnung für den Nicht-VLA-Fall aus.

Lundin
quelle
34
"es muss tatsächlich einen Fehler geben" - die Unterscheidung zwischen "Warnungen" und "Fehlern" wird im Standard nicht erkannt (es wird nur "Diagnose" erwähnt) und die einzige Situation, in der die Kompilierung aufhören muss [dh der reale Unterschied zwischen Warnung und Fehler] ist auf eine #errorAnweisung gestoßen .
Random832
12
Zu Ihrer Information, in der Regel geben die (C- oder C ++) Standards nur an, was Compiler zulassen müssen , nicht jedoch, was sie nicht zulassen müssen . In einigen Fällen geben sie an, dass der Compiler eine "Diagnose" ausgeben soll, aber das ist ungefähr so ​​spezifisch wie sie bekommen. Der Rest bleibt dem Compilerhersteller überlassen. EDIT: Was Random832 auch gesagt hat.
mcmcc
8
@Lundin "Ein Compiler darf keine Binärdatei mit Arrays mit der Länge Null erstellen." Der Standard sagt absolut nichts dergleichen. Es wird nur angegeben, dass mindestens eine Diagnosemeldung generiert werden muss, wenn der Quellcode ein Array mit einem konstanten Ausdruck der Länge Null für seine Größe enthält. Der einzige Umstand, unter dem der Standard einem Compiler verbietet, eine Binärdatei zu erstellen, ist, wenn er auf eine #errorPräprozessor-Direktive stößt .
Random832
5
@Lundin Das Generieren einer Binärdatei für alle korrekten Fälle erfüllt die Nummer 1, und das Generieren oder Nichtgenerieren einer Binärdatei für falsche Fälle hat keine Auswirkungen darauf. Das Drucken einer Warnung ist für # 3 ausreichend. Dieses Verhalten hat keine Relevanz für # 2, da der Standard das Verhalten dieses Quellcodes nicht definiert.
Random832
13
@Lundin: Der Punkt ist, dass Ihre Aussage falsch ist; konformen Compilern werden erlaubt , eine binäre zu bauen eine Null-Länge - Arrays enthält, so lange , wie ein diagnostisches ausgegeben wird.
Keith Thompson
85

Gemäß dem Standard ist es nicht erlaubt.

In C-Compilern ist es jedoch derzeit üblich, diese Deklarationen als FAM- Deklaration (Flexible Array Member ) zu behandeln :

C99 6.7.2.1, §16 : 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-Mitglied bezeichnet.

Die Standardsyntax eines FAM lautet:

struct Array {
  size_t size;
  int content[];
};

Die Idee ist, dass Sie es dann so zuweisen würden:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

Sie können es auch statisch verwenden (gcc-Erweiterung):

Array a = { 3, { 1, 2, 3 } };

Dies wird auch als schwanzgepolsterte Strukturen (dieser Begriff stammt aus der Zeit vor der Veröffentlichung des C99-Standards) oder als Struktur-Hack (danke an Joe Wreschnig für den Hinweis) bezeichnet.

Diese Syntax wurde jedoch erst kürzlich in C99 standardisiert (und die Effekte garantiert). Vorher war eine konstante Größe notwendig.

  • 1 war der tragbare Weg, obwohl es ziemlich seltsam war.
  • 0 war besser in der Anzeige von Absichten, aber in Bezug auf den Standard nicht legal und wurde von einigen Compilern (einschließlich gcc) als Erweiterung unterstützt.

Die Praxis der Schwanzpolsterung beruht jedoch auf der Tatsache, dass Speicher verfügbar ist (vorsichtig malloc) und daher im Allgemeinen nicht für die Stapelverwendung geeignet ist .

Matthieu M.
quelle
@Lundin: Ich habe hier keine VLA gesehen, alle Größen sind zur Kompilierungszeit bekannt. Der Begriff des flexiblen Arrays stammt von gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html und qualifiziert sich int content[];hier, soweit ich weiß . Da ich in Bezug auf C nicht allzu versiert bin ... können Sie bestätigen, ob meine Argumentation richtig erscheint?
Matthieu M.
@MatthieuM.: C99 6.7.2.1, §16: 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-Mitglied bezeichnet.
Christoph
Diese Redewendung ist auch unter dem Namen "struct hack" bekannt , und ich habe mehr Leute getroffen, die mit diesem Namen vertraut sind als "tail-gepolsterte Struktur" (noch nie zuvor gehört, außer vielleicht als allgemeine Referenz zum Auffüllen einer Struktur für die zukünftige ABI-Kompatibilität ) oder "Flexible Array Member", das ich zum ersten Mal in C99 gehört habe.
1
Die Verwendung einer Array-Größe von 1 für den Struktur-Hack würde das Quietschen von Compilern vermeiden, war jedoch nur "portabel", da Compiler-Autoren nett genug waren, um eine solche Verwendung als De-facto-Standard anzuerkennen. Wäre nicht das Verbot von Arrays mit der Größe Null, die konsequente Verwendung von Einzelelement-Arrays durch den Programmierer als mieser Ersatz und die historische Haltung der Compiler-Autoren, dass sie den Anforderungen des Programmierers gerecht werden sollten, selbst wenn dies vom Standard nicht verlangt wird, könnten Compiler-Autoren dies tun haben einfach und sinnvoll optimiert foo[x], foo[0]wann immer fooein Einzelelement-Array war.
Supercat
1
@ RobertSsupportsMonicaCellio: Es wird explizit in der Antwort gezeigt, aber am Ende . Ich habe die Erklärung auch von vorne geladen, um sie von Anfang an klarer zu machen.
Matthieu M.
58

In Standard C und C ++ ist ein Array mit der Größe Null nicht zulässig.

Wenn Sie GCC verwenden, kompilieren Sie es mit der -pedanticOption. Es wird eine Warnung geben und sagen:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

Im Fall von C ++ gibt es eine ähnliche Warnung.

Nawaz
quelle
9
In Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Mark Ransom
4
-Werror verwandelt einfach alle Warnungen in Fehler, die das falsche Verhalten des GCC-Compilers nicht beheben.
Lundin
C ++ Builder 2009 gibt auch korrekt einen Fehler aus:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin
1
Stattdessen -pedantic -Werrorkönnten Sie auch einfach tun-pedantic-errors
Stephan Dollberg
3
Ein Array mit der Größe Null ist nicht ganz dasselbe wie ein Array mit der Größe Null std::array. (Nebenbei: Ich erinnere mich, kann aber die Quelle nicht finden, aus der VLAs in C ++ berücksichtigt und ausdrücklich abgelehnt wurden.)
27

Es ist völlig illegal und war es schon immer, aber viele Compiler vernachlässigen es, den Fehler zu signalisieren. Ich bin mir nicht sicher, warum du das machen willst. Die einzige mir bekannte Verwendung besteht darin, einen Kompilierungszeitfehler von einem Booleschen Wert auszulösen:

char someCondition[ condition ];

Wenn dies conditionfalsch ist, wird ein Fehler bei der Kompilierung angezeigt. Da Compiler dies jedoch zulassen, habe ich Folgendes verwendet:

char someCondition[ 2 * condition - 1 ];

Dies ergibt eine Größe von 1 oder -1, und ich habe noch nie einen Compiler gefunden, der eine Größe von -1 akzeptiert.

James Kanze
quelle
Dies ist ein interessanter Hack, für den man ihn verwenden kann.
Alex Koay
10
Ich denke, das ist ein gängiger Trick bei der Metaprogrammierung. Es würde mich nicht wundern, wenn die Implementierungen STATIC_ASSERTes verwenden würden.
James Kanze
Warum nicht einfach:#if condition \n #error whatever \n #endif
Jerfov2
1
@ Jerfov2, weil die Bedingung zur Vorverarbeitungszeit möglicherweise nicht bekannt ist, nur Kompilierungszeit
rmeador
9

Ich werde hinzufügen, dass es zu diesem Argument eine ganze Seite der Online-Dokumentation von gcc gibt.

Einige Zitate:

Arrays mit der Länge Null sind in GNU C zulässig.

In ISO C90 müssten Sie dem Inhalt eine Länge von 1 geben

und

Mit GCC-Versionen vor 3.0 konnten Arrays mit der Länge Null statisch initialisiert werden, als wären sie flexible Arrays. Zusätzlich zu den nützlichen Fällen wurden Initialisierungen in Situationen zugelassen, die spätere Daten beschädigen würden

so könntest du

int arr[0] = { 1 };

und boom :-)

Xanatos
quelle
Kann ich wie tun int a[0], dann a[0] = 1 a[1] = 2??
Suraj Jain
2
@SurajJain Wenn Sie Ihren Stack überschreiben möchten :-) C überprüft den Index nicht anhand der Größe des Arrays, das Sie schreiben. Sie können dies also, a[100000] = 5aber wenn Sie Glück haben, stürzen Sie Ihre App einfach ab, wenn Sie Glück haben: -)
Xanatos
Int a [0]; bedeutet ein variables Array (Array mit der Größe Null). Wie kann ich es jetzt zuweisen
?
@SurajJain Welcher Teil von "C überprüft den Index nicht gegenüber der Größe des Arrays, das Sie schreiben" ist nicht klar? In C gibt es keine Indexprüfung. Sie können nach dem Ende des Arrays schreiben und den Computer zum Absturz bringen oder wertvolle Teile Ihres Speichers überschreiben. Wenn Sie also ein Array mit 0 Elementen haben, können Sie nach dem Ende der 0 Elemente schreiben.
Xanatos
Siehe diese quora.com/…
Suraj Jain
9

Eine andere Verwendung von Arrays mit der Länge Null besteht darin, Objekte mit variabler Länge (vor C99) zu erstellen. Nulllängen - Arrays sind verschieden von flexiblen Arrays denen [] ohne 0.

Zitiert aus gcc doc :

Arrays mit der Länge Null sind in GNU C zulässig. Sie sind sehr nützlich als letztes Element einer Struktur, die wirklich ein Header für ein Objekt mit variabler Länge ist:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

In ISO C99 würden Sie ein flexibles Array-Element verwenden, das sich in Syntax und Semantik geringfügig unterscheidet:

  • Flexible Array-Mitglieder werden als Inhalt [] ohne die 0 geschrieben.
  • Flexible Array-Elemente haben einen unvollständigen Typ, sodass der Operator sizeof möglicherweise nicht angewendet wird.

Ein Beispiel aus der Praxis sind Arrays mit der Länge Null struct kdbus_itemin kdbus.h (einem Linux-Kernelmodul).

Herzog
quelle
2
Meiner Meinung nach gab es keinen guten Grund für den Standard, Arrays mit der Länge Null zu verbieten. Es könnte Objekte mit der Größe Null haben, die als Elemente einer Struktur gut void*geeignet sind, und sie als arithmetische Zwecke betrachten (daher wäre das Hinzufügen oder Subtrahieren von Zeigern auf Objekte mit der Größe Null verboten). Während flexible Array-Mitglieder meistens besser sind als Arrays mit der Größe Null, können sie auch als eine Art "Vereinigung" von Alias-Dingen fungieren, ohne dem Folgenden eine zusätzliche Ebene der "syntaktischen" Indirektion hinzuzufügen (z. B. wenn struct foo {unsigned char as_bytes[0]; int x,y; float z;}man auf Mitglieder zugreifen kann) x. z...
Supercat
... direkt, ohne zB myStruct.asFoo.xusw. sagen zu müssen. Außerdem kreischt IIRC, C bei jedem Versuch, ein flexibles Array-Element in eine Struktur aufzunehmen, wodurch es unmöglich wird, eine Struktur zu haben, die mehrere andere flexible Array-Elemente bekannter Länge enthält Inhalt.
Supercat
@supercat Ein guter Grund ist, die Integrität der Regel für den Zugriff auf externe Array-Grenzen aufrechtzuerhalten. Als letztes Mitglied einer Struktur erzielt das flexible C99- Array-Mitglied genau den gleichen Effekt wie das GCC-Array mit der Größe Null, ohne dass Sonderregeln zu anderen Regeln hinzugefügt werden müssen. IMHO ist es eine Verbesserung, sizeof x->contentsdie ein Fehler in ISO C ist, im Gegensatz zur Rückgabe von 0 in gcc. Arrays mit der Größe Null, die keine Strukturelemente sind, führen zu einer Reihe anderer Probleme.
MM
@MM: Welche Probleme würden sie verursachen, wenn das Subtrahieren von zwei gleichen Zeigern auf ein Objekt mit der Größe Null als Ausbeute Null definiert würde (ebenso wie das Subtrahieren gleicher Zeiger auf Objekte jeder Größe) und das Subtrahieren ungleicher Zeiger auf Objekte mit der Größe Null als Ausbeute definiert würde Nicht spezifizierter Wert? Wenn der Standard angegeben hätte, dass eine Implementierung das Einbetten einer Struktur mit einem FAM in eine andere Struktur ermöglichen könnte, vorausgesetzt, das nächste Element in der letzteren Struktur ist entweder ein Array mit demselben Elementtyp wie das FAM oder eine Struktur, die mit einem solchen Array beginnt und vorausgesetzt, dass ...
Supercat
... erkennt die FAM als Aliasing des Arrays (wenn Ausrichtungsregeln dazu führen würden, dass die Arrays bei unterschiedlichen Offsets landen, wäre eine Diagnose erforderlich), wäre dies sehr nützlich gewesen. So wie es ist, gibt es keine gute Möglichkeit, eine Methode zu haben, die Zeiger auf Strukturen des allgemeinen Formats akzeptiert struct {int n; THING dat[];}und mit Dingen von statischer oder automatischer Dauer arbeiten kann.
Supercat
6

Array-Deklarationen mit der Größe Null innerhalb von Strukturen wären nützlich, wenn sie zulässig wären und wenn die Semantik so wäre, dass (1) sie die Ausrichtung erzwingen, aber ansonsten keinen Platz zuweisen würden, und (2) die Indizierung des Arrays als definiertes Verhalten in der Struktur betrachtet würde Fall, in dem sich der resultierende Zeiger im selben Speicherblock wie die Struktur befindet. Ein solches Verhalten wurde von keinem C-Standard zugelassen, aber einige ältere Compiler erlaubten es, bevor es zum Standard für Compiler wurde, unvollständige Array-Deklarationen mit leeren Klammern zuzulassen.

Der Struktur-Hack, wie er üblicherweise mit einem Array der Größe 1 implementiert wird, ist zweifelhaft und ich glaube nicht, dass Compiler es unterlassen müssen, ihn zu brechen. Zum Beispiel würde ich erwarten, dass ein Compiler, wenn er dies sieht int a[1], in seinem Recht liegt, dies a[i]als zu betrachten a[0]. Wenn jemand versucht, die Ausrichtungsprobleme des Struktur-Hacks über so etwas zu umgehen

typedef struct {
  uint32_t Größe;
  uint8_t Daten [4]; // Verwenden Sie vier, um zu vermeiden, dass das Auffüllen die Größe der Struktur beeinträchtigt
}}

Ein Compiler könnte klug werden und davon ausgehen, dass die Array-Größe tatsächlich vier beträgt:

;; Wie geschrieben
  foo = myStruct-> data [i];
;; Wie interpretiert (unter der Annahme von Little-Endian-Hardware)
  foo = ((* (uint32_t *) myStruct-> data) >> (i << 3)) & 0xFF;

Eine solche Optimierung könnte sinnvoll sein, insbesondere wenn myStruct->datasie in derselben Operation wie in ein Register geladen werden könnte myStruct->size. Ich weiß nichts in dem Standard, was eine solche Optimierung verbieten würde, obwohl es natürlich jeden Code brechen würde, der erwarten könnte, auf Dinge über das vierte Element hinaus zuzugreifen.

Superkatze
quelle
1
Das flexible Array-Mitglied wurde C99 als legitime Version des Struktur-Hacks hinzugefügt
MM
Der Standard besagt, dass Zugriffe auf verschiedene Array-Mitglieder nicht in Konflikt stehen, was diese Optimierung tendenziell unmöglich machen würde.
Ben Voigt
@BenVoigt: Der C-Sprachstandard gibt nicht an, wie ein Byte geschrieben und ein Wort gleichzeitig gelesen wird. 99,9% der Prozessoren geben jedoch an, dass der Schreibvorgang erfolgreich ist und das Wort entweder die neue oder die alte Version von enthält Byte zusammen mit dem unveränderten Inhalt der anderen Bytes. Wenn ein Compiler auf solche Prozessoren abzielt, was wäre der Konflikt?
Supercat
@supercat: Der C-Sprachstandard garantiert, dass gleichzeitige Schreibvorgänge in zwei verschiedene Array-Elemente keine Konflikte verursachen. Ihr Argument, dass (Lesen während Schreiben) in Ordnung ist, reicht also nicht aus.
Ben Voigt
@BenVoigt: Wenn ein Code z. B. in einer bestimmten Reihenfolge in die Array-Elemente 0, 1 und 2 schreiben würde, wäre es nicht zulässig, alle vier Elemente zu einem langen zu lesen, drei zu ändern und alle vier zurückzuschreiben, aber ich Ich denke, es wäre erlaubt, alle vier in eine lange zu lesen, drei zu modifizieren, die unteren 16 Bits als kurze und die Bits 16-23 als Byte zurückzuschreiben. Würden Sie dem nicht zustimmen? Und Code, der nur Elemente des Arrays lesen musste, durfte sie einfach in eine lange lesen und diese verwenden.
Supercat