Warum können C-Arrays keine 0-Länge haben?

13

Der C11-Standard besagt, dass die Arrays in Größe und variabler Länge "einen Wert größer als Null haben sollen". Was ist die Rechtfertigung dafür, eine Länge von 0 nicht zuzulassen?

Insbesondere für Arrays mit variabler Länge ist es durchaus sinnvoll, ab und zu eine Größe von Null zu haben. Es ist auch nützlich für statische Arrays, wenn ihre Größe aus einem Makro oder einer Build-Konfigurationsoption stammt.

Interessanterweise bieten GCC (und Clang) Erweiterungen, die Arrays mit einer Länge von Null ermöglichen. Java erlaubt auch Arrays der Länge Null.

Kevin Cox
quelle
7
stackoverflow.com/q/8625572 ... „Ein Array der Länge Null wäre kompliziert und verwirrend mit der Forderung in Einklang zu bringen , dass jedes Objekt eine eindeutige Adresse hat.“
Robert Harvey
3
@RobertHarvey: Gegeben struct { int p[1],q[1]; } foo; int *pp = p+1;, ppwäre ein legitimer Zeiger, *pphätte aber keine eindeutige Adresse. Warum könnte dieselbe Logik bei einem Array mit Nulllänge nicht gelten? Sagen Sie, dass gegebene int q[0]; innerhalb einer Struktur , qzu einer Adresse , deren Gültigkeit wie die der wäre beziehen würde p+1Beispiel oben.
Supercat
@DocBrown Nach dem C11-Standard 6.7.6.2.5 wird über den Ausdruck gesprochen, mit dem die Größe einer VLA bestimmt wird. Ich weiß nichts über C99 (und es scheint seltsam, dass sie es ändern würden), aber es hört sich so an, als ob Sie keine Länge von Null haben können.
Kevin Cox
@ KevinCox: Gibt es eine kostenlose Online-Version des C11-Standards (oder des betreffenden Teils)?
Doc Brown
Die endgültige Version ist nicht kostenlos erhältlich (was für eine Schande), aber Sie können Entwürfe herunterladen. Der letzte verfügbare Entwurf ist open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf .
Kevin Cox

Antworten:

11

Das Problem, auf das ich wetten würde, ist, dass C-Arrays nur Zeiger auf den Anfang eines zugewiesenen Speicherbereichs sind. Eine 0-Größe würde bedeuten, dass Sie einen Zeiger auf ... nichts haben? Du kannst nichts haben, also hätte es eine willkürliche Entscheidung geben müssen. Sie können nicht verwenden null, da Ihre Arrays mit der Länge 0 dann wie Nullzeiger aussehen würden. Und zu diesem Zeitpunkt wird jede andere Implementierung unterschiedliche willkürliche Verhaltensweisen auswählen, was zu Chaos führt.

Telastyn
quelle
7
@delnan: Nun, wenn Sie pedantisch vorgehen möchten, ist die Array- und Zeigerarithmetik so definiert, dass ein Zeiger bequem verwendet werden kann, um auf ein Array zuzugreifen oder ein Array zu simulieren. Mit anderen Worten, es sind Zeigerarithmetik und Array-Indizierung, die in C äquivalent sind. Aber das Ergebnis ist trotzdem dasselbe ... Wenn die Länge des Arrays Null ist, zeigen Sie immer noch auf nichts.
Robert Harvey
3
@RobertHarvey Alles wahr, aber Ihre abschließenden Worte (und die gesamte Antwort im Nachhinein) scheinen nur eine verwirrende und verwirrende Erklärung dafür zu sein, dass ein solches Array (ich denke , das ist, was diese Antwort "einen zugewiesenen Speicherblock" nennt?) Hätte sizeof0, und wie das Ärger verursachen würde. All dies kann mit den richtigen Konzepten und Begriffen erklärt werden, ohne an Kürze oder Klarheit zu verlieren. Das Verwechseln von Arrays und Zeigern birgt nur das Risiko, die Arrays zu verbreiten = Zeiger-Missverständnisse (was in anderen Zusammenhängen wichtiger ist), die keinen Nutzen bringen.
2
" Sie können null nicht verwenden, da Ihre Arrays mit der Länge 0 dann wie Nullzeiger aussehen " - genau das macht Delphi. Leere Dynarrays und leere Longstrings sind technisch gesehen Nullzeiger.
JensG
3
-1, ich bin voll mit @delnan hier. Dies erklärt nichts, insbesondere im Zusammenhang mit dem, was das OP über einige wichtige Compiler schrieb, die das Konzept der Nulllängen-Arrays unterstützen. Ich bin mir ziemlich sicher, dass Arrays mit der Länge Null in C implementierungsunabhängig bereitgestellt werden können, ohne dass dies zu "Chaos" führt.
Doc Brown
6

Schauen wir uns an, wie ein Array normalerweise im Speicher angeordnet ist:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

Beachten Sie, dass es kein separates Objekt gibt arr, das die Adresse des ersten Elements speichert. Wenn ein Array in einem Ausdruck erscheint, berechnet C die Adresse des ersten Elements nach Bedarf.

Also, lassen Sie uns darüber nachdenken: ein 0-Element - Array haben würde keine Lagerung beiseite Satz für sie, was bedeutet , es gibt nichts , das Array - Adresse zu berechnen aus (anders ausgedrückt, gibt es keine Objektzuordnung für den Identifier). Es ist so, als würde man sagen: "Ich möchte eine intVariable erstellen , die keinen Speicher belegt." Es ist eine unsinnige Operation.

Bearbeiten

Java-Arrays unterscheiden sich grundlegend von C- und C ++ - Arrays. Sie sind kein primitiver Typ, sondern ein Referenztyp, von dem sie abgeleitet sind Object.

Bearbeiten 2

Ein in den Kommentaren unten dargestellter Punkt - Die Einschränkung "größer als 0" gilt nur für Arrays, deren Größe durch einen konstanten Ausdruck angegeben wird . Eine VLA darf eine Länge von 0 haben. Das Deklarieren einer VLA mit einem 0-wertigen nicht konstanten Ausdruck ist keine Einschränkungsverletzung, ruft jedoch undefiniertes Verhalten auf.

Es ist klar, dass VLAs andere Tiere sind als reguläre Arrays , und ihre Implementierung kann eine Größe von 0 zulassen . Sie können nicht deklariert staticoder im Dateibereich gespeichert werden, da die Größe solcher Objekte bekannt sein muss, bevor das Programm gestartet wird.

Es ist auch nichts wert, dass ab C11 keine Implementierungen erforderlich sind, um VLAs zu unterstützen.

John Bode
quelle
3
Tut mir leid, aber meiner Meinung nach fehlt Ihnen genau wie Telastyn der springende Punkt. Nulllängen-Arrays können sehr sinnvoll sein, und vorhandene Implementierungen, wie sie uns vom OP mitgeteilt wurden, zeigen, dass dies möglich ist.
Doc Brown
@ DocBrown: Zuerst habe ich angesprochen, warum der Sprachstandard sie höchstwahrscheinlich nicht zulässt. Zweitens möchte ich ein Beispiel dafür geben, wo ein Array der Länge 0 Sinn macht, weil mir ehrlich gesagt keines einfällt. Die wahrscheinlichste Implementierung ist zu behandeln T a[0]als T *a, aber warum nicht einfach verwenden T *a?
John Bode
Tut mir leid, aber ich kaufe keine "theoretischen Überlegungen", warum der Standard dies verbietet. Lesen Sie meine Antwort, wie die Adresse tatsächlich leicht berechnet werden könnte. Und ich schlage vor, Sie folgen dem Link in Robert Harveys erstem Kommentar unter der Frage und lesen die zweite Antwort, es gibt ein nützliches Beispiel.
Doc Brown
@ DocBrown: Ah. Der structHack. Ich habe es nie persönlich benutzt; Ich habe nie an einem Problem gearbeitet, das einen structTyp mit variabler Größe benötigte .
John Bode
2
Und nicht zu vergessen, dass AFAIK seit C99 Arrays mit variabler Länge zulässt. Und wenn es sich bei der Arraygröße um einen Parameter handelt, können viele Programme einfacher sein, wenn Sie den Wert 0 nicht als Sonderfall behandeln müssen.
Doc Brown
2

Normalerweise möchten Sie, dass das Array mit der Größe Null (tatsächlich variabel) zur Laufzeit die Größe kennt. Dann packe das in ein structund verwende flexible Array-Mitglieder , wie zB:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

Offensichtlich muss das flexible Array-Mitglied das letzte sein, structund Sie müssen vorher etwas haben. Häufig hängt dies mit der tatsächlichen Laufzeitlänge dieses flexiblen Arraymitglieds zusammen.

Natürlich würden Sie zuordnen:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, das war schon in C99 möglich und es ist sehr nützlich.

Übrigens gibt es in C ++ keine flexiblen Array-Mitglieder (da es schwierig ist zu definieren, wann und wie sie erstellt und zerstört werden sollen). Siehe jedoch den zukünftigen std :: dynarray

Basile Starynkevitch
quelle
Wissen Sie, sie könnten nur auf triviale Typen beschränkt sein, und es würde keine Schwierigkeiten geben.
Deduplizierer
2

Wenn der Ausdruck type name[count]in einer Funktion geschrieben ist, weisen Sie den C-Compiler an, die Stack-Frame- sizeof(type)*countBytes zuzuweisen und die Adresse des ersten Elements im Array zu berechnen.

Ist der Ausdruck type name[count] außerhalb aller Funktionen und Strukturdefinitionen geschrieben wird, weisen Sie den C-Compiler an, die Datensegmentbytes zuzuweisen sizeof(type)*countund die Adresse des ersten Elements im Array zu berechnen.

nameTatsächlich ist es ein konstantes Objekt, das die Adresse des ersten Elements im Array speichert. Jedes Objekt, das eine Adresse eines Speichers speichert, wird als Zeiger bezeichnet. Aus diesem Grund behandeln Sie diesen nameals Zeiger und nicht als Array. Beachten Sie, dass auf Arrays in C nur über Zeiger zugegriffen werden kann.

Wenn count es sich um einen konstanten Ausdruck handelt, der zu Null ausgewertet wird, weisen Sie den C-Compiler an, entweder im Stack-Frame oder im Datensegment Null-Bytes zuzuweisen und die Adresse des ersten Elements im Array zurückzugeben. Das Problem dabei ist jedoch, dass das erste Element ist Array mit der Länge Null existiert nicht und Sie können die Adresse von etwas, das nicht existiert, nicht berechnen.

Dies ist rational das Element Nr. count+1existiert nicht im count-length-Array, daher verbietet der C-Compiler, ein Array mit der Länge Null als Variable innerhalb und außerhalb einer Funktion zu definieren. Worin besteht dann der Inhalt name? Welche Adressename speichert genau?

Wenn pes sich um einen Zeiger handelt, ist der Ausdruck p[n]äquivalent zu*(p + n)

Wenn das Sternchen * im rechten Ausdruck eine dereferenzierte Operation des Zeigers ist, dh auf den Speicher zugreifen, auf den verwiesen wird, p + noder auf den Speicher, in dem die Adresse gespeichert ist p + n, und p + nZeigerausdruck, nimmt er die Adresse von pund nmultipliziert die Zahl mit dieser Adresse Größe des Zeigertyps p.

Ist es möglich, eine Adresse und eine Nummer hinzuzufügen?

Ja, das ist möglich, da es sich bei der Adresse um eine vorzeichenlose Ganzzahl handelt, die üblicherweise in hexadezimaler Schreibweise dargestellt wird.

user307542
quelle
Viele Compiler erlaubten Array-Deklarationen der Größe Null, bevor der Standard dies verbot, und viele erlauben solche Deklarationen weiterhin als Erweiterung. Diese Erklärungen werden kein Problem verursachen , wenn man ein Objekt der Größe erkennt , dass Nhat N+1Adressen zugeordnet, die erste Nvon denen einzigartige Bytes und die letzte identifizieren , Nvon denen gerade hinter einer jener Bytes jeden Punkt. Eine solche Definition würde auch in dem entarteten Fall gut funktionieren, in dem N0 ist.
supercat
1

Wenn Sie einen Zeiger auf eine Speicheradresse möchten, deklarieren Sie einen. Ein Array zeigt tatsächlich auf einen Speicherbereich, den Sie reserviert haben. Arrays verwandeln sich in Zeiger, wenn sie an Funktionen übergeben werden, aber wenn sich der Speicher, auf den sie zeigen, auf dem Heap befindet, ist dies kein Problem. Es gibt keinen Grund, ein Array der Größe Null zu deklarieren.

ncmathsadist
quelle
2
Im Allgemeinen würden Sie dies nicht direkt tun, sondern als Ergebnis eines Makros oder beim Deklarieren eines Arrays variabler Länge mit dynamischen Daten.
Kevin Cox
Ein Array zeigt niemals. Es kann Zeiger enthalten, und in den meisten Kontexten verwenden Sie tatsächlich einen Zeiger auf das erste Element, aber das ist eine andere Geschichte.
Deduplizierer
1
Der Arrayname ist ein konstanter Zeiger auf den im Array enthaltenen Speicher.
Ncmathsadist
1
Nein, der Array-Name verwandelt sich in den meisten Kontexten in einen Zeiger auf das erste Element. Der Unterschied ist oft entscheidend.
Deduplizierer
1

Aus den Tagen des ursprünglichen C89, als ein C-Standard festlegte, dass etwas undefiniertes Verhalten aufwies, bedeutete dies "Was auch immer eine Implementierung auf einer bestimmten Zielplattform am besten für den beabsichtigten Zweck geeignet machen würde". Die Autoren des Standards wollten nicht erraten, welches Verhalten für einen bestimmten Zweck am besten geeignet ist. Bestehende C89-Implementierungen mit VLA-Erweiterungen hatten möglicherweise ein anderes, aber logisches Verhalten, wenn eine Größe von Null angegeben wurde (z. B. haben einige das Array möglicherweise als Adressausdruck behandelt, der NULL ergibt, während andere es als Adressausdruck behandeln, der der Adresse von entspricht eine andere willkürliche Variable, die aber ohne weiteres mit Null versehen werden kann, ohne dass ein Trapping erfolgt). Wenn sich ein Code auf ein derart unterschiedliches Verhalten verlassen hätte, würden die Autoren des Standards nicht

Anstatt zu erraten, was Implementierungen bewirken könnten, oder anzunehmen, dass ein Verhalten einem anderen überlegen sein sollte, erlaubten die Autoren des Standards den Implementierern lediglich , den Fall nach eigenem Ermessen zu beurteilen . Implementierungen, die hinter den Kulissen malloc () verwenden, können die Adresse des Arrays als NULL behandeln (wenn malloc mit der Größe Null null ergibt), diejenigen, die Stapeladressenberechnungen verwenden, können einen Zeiger ergeben, der mit der Adresse einer anderen Variablen übereinstimmt, und einige andere Implementierungen können dies tun andere Dinge. Ich glaube nicht, dass sie damit gerechnet haben, dass Compiler-Autoren alles daran setzen, dass sich der Eckfall mit der Größe Null absichtlich nutzlos verhält.

Superkatze
quelle