Wenn Sie in der Sprache C ein Array wie folgt initialisieren:
int a[5] = {1,2};
dann werden alle Elemente des Arrays, die nicht explizit initialisiert wurden, implizit mit Nullen initialisiert.
Aber wenn ich ein Array wie dieses initialisiere:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
Ausgabe:
1 0 1 0 0
Ich verstehe nicht, warum a[0]
drucken 1
statt 0
? Ist es undefiniertes Verhalten?
Hinweis: Diese Frage wurde in einem Interview gestellt.
a[2]=1
ergibt1
.a[2] = 1
ist1
, aber ich bin nicht sicher , ob Sie das Ergebnis eines bestimmten initialiser Ausdrucks als der Wert des ersten Elements nehmen dürfen. Die Tatsache, dass Sie das Anwalt-Tag hinzugefügt haben, bedeutet, dass wir eine Antwort brauchen, die den Standard zitiert.Antworten:
TL; DR: Ich denke nicht, dass das Verhalten von
int a[5]={a[2]=1};
gut definiert ist, zumindest in C99.Der lustige Teil ist, dass das einzige Bit, das für mich Sinn macht, der Teil ist, nach dem Sie fragen:
a[0]
wird gesetzt,1
weil der Zuweisungsoperator den zugewiesenen Wert zurückgibt. Alles andere ist unklar.Wenn der Code gewesen wäre
int a[5] = { [2] = 1 }
, wäre alles einfach gewesen: Das ist eine festgelegte Initialisierungseinstellunga[2]
für1
und alles andere für0
. Aber mit haben{ a[2] = 1 }
wir einen nicht bezeichneten Initialisierer, der einen Zuweisungsausdruck enthält, und wir fallen in ein Kaninchenloch.Folgendes habe ich bisher gefunden:
a
muss eine lokale Variable sein.a[2] = 1
ist kein konstanter Ausdruck,a
muss also automatisch gespeichert werden.a
ist im Umfang in seiner eigenen Initialisierung.Der Deklarator ist
a[5]
, also sind Variablen in ihrer eigenen Initialisierung im Geltungsbereich.a
lebt in seiner eigenen Initialisierung.Es gibt einen Sequenzpunkt danach
a[2]=1
.Beachten Sie, dass sich z. B. in
int foo[] = { 1, 2, 3 }
dem{ 1, 2, 3 }
Teil eine in Klammern eingeschlossene Liste von Initialisierern befindet, nach denen jeweils ein Sequenzpunkt steht.Die Initialisierung erfolgt in der Reihenfolge der Initialisierungsliste.
Initialisierungsausdrücke werden jedoch nicht unbedingt der Reihe nach ausgewertet.
Dennoch bleiben einige Fragen offen:
Sind Sequenzpunkte überhaupt relevant? Die Grundregel lautet:
a[2] = 1
ist ein Ausdruck, die Initialisierung jedoch nicht.Dies wird in Anhang J leicht widerlegt:
Anhang J besagt, dass jede Änderung zählt, nicht nur Änderungen durch Ausdrücke. Da Anhänge jedoch nicht normativ sind, können wir dies wahrscheinlich ignorieren.
Wie werden die Unterobjektinitialisierungen in Bezug auf Initialisiererausdrücke sequenziert? Werden zuerst alle Initialisierer ausgewertet (in einer bestimmten Reihenfolge), dann werden die Unterobjekte mit den Ergebnissen initialisiert (in der Reihenfolge der Initialisiererlisten)? Oder können sie verschachtelt werden?
Ich denke
int a[5] = { a[2] = 1 }
wird wie folgt ausgeführt:a
wird zugewiesen, wenn der enthaltende Block eingegeben wird. Der Inhalt ist zu diesem Zeitpunkt unbestimmt.a[2] = 1
), gefolgt von einem Sequenzpunkt. Dies speichert1
ina[2]
und kehrt zurück1
.1
wird zum Initialisieren verwendeta[0]
(der erste Initialisierer initialisiert das erste Unterobjekt).Aber hier Dinge Fuzzy weil die übrigen Elemente (
a[1]
,a[2]
,a[3]
,a[4]
) sollen initialisiert werden0
, aber es ist nicht klar , wann: Ist es schon mal vorkommena[2] = 1
ausgewertet wird? Wenn ja,a[2] = 1
würde "gewinnen" und überschreibena[2]
, aber hätte diese Zuweisung ein undefiniertes Verhalten, da zwischen der Nullinitialisierung und dem Zuweisungsausdruck kein Sequenzpunkt liegt? Sind Sequenzpunkte überhaupt relevant (siehe oben)? Oder erfolgt eine Nullinitialisierung, nachdem alle Initialisierer ausgewertet wurden? Wenn ja,a[2]
sollte am Ende sein0
.Da der C-Standard nicht klar definiert, was hier passiert, glaube ich, dass das Verhalten undefiniert ist (durch Auslassung).
quelle
a[0]
Unterobjekt vor der Auswertung seines Initialisierers nicht initialisieren können und die Auswertung eines Initialisierers einen Sequenzpunkt enthält (da es sich um einen "vollständigen Ausdruck" handelt). Daher glaube ich, dass das Ändern des Teilobjekts, das wir initialisieren, ein faires Spiel ist.Vermutlich wird zuerst
a[2]=1
initialisierta[2]
, und das Ergebnis des Ausdrucks wird zur Initialisierung verwendeta[0]
.Aus N2176 (Entwurf C17):
Es scheint also, dass auch eine Ausgabe
1 0 0 0 0
möglich gewesen wäre.Fazit: Schreiben Sie keine Initialisierer, die die initialisierte Variable im laufenden Betrieb ändern.
quelle
{...}
Ausdruck, der initialisierta[2]
zu0
unda[2]=1
Unterausdruck , der initialisierta[2]
zu1
.{...}
ist eine geschweifte Initialisiererliste. Es ist kein Ausdruck.Ich denke, der C11-Standard deckt dieses Verhalten ab und sagt, dass das Ergebnis nicht spezifiziert ist , und ich denke, dass C18 keine relevanten Änderungen in diesem Bereich vorgenommen hat.
Die Standardsprache ist nicht einfach zu analysieren. Der relevante Abschnitt der Norm ist §6.7.9 Initialisierung . Die Syntax ist dokumentiert als:
Beachten Sie, dass einer der Begriffe Zuweisungsausdruck ist. Da
a[2] = 1
es sich zweifellos um einen Zuweisungsausdruck handelt, ist er in Initialisierern für Arrays mit nicht statischer Dauer zulässig:Einer der wichtigsten Absätze ist:
Und ein weiterer wichtiger Absatz ist:
Ich bin mir ziemlich sicher, dass Absatz 23 die Notation in der Frage angibt:
führt zu nicht näher bezeichnetem Verhalten. Die Zuordnung zu
a[2]
ist ein Nebeneffekt, und die Bewertungsreihenfolge der Ausdrücke ist unbestimmt in Bezug zueinander geordnet. Folglich glaube ich nicht, dass es eine Möglichkeit gibt, sich auf den Standard zu berufen und zu behaupten, dass ein bestimmter Compiler dies richtig oder falsch handhabt.quelle
Mein Verständnis ist, dass
a[2]=1
der Wert 1 zurückgegeben wird, sodass Code wirdint a[5]={1}
Wert für a [0] = 1 zuweisenDaher druckt es 1 für eine [0]
Beispielsweise
quelle
Ich versuche eine kurze und einfache Antwort auf das Rätsel zu geben:
int a[5] = { a[2] = 1 };
a[2] = 1
wird gesetzt. Das heißt, das Array sagt:0 0 1 0 0
{ }
Klammern , die verwendet werden, um das Array in der richtigen Reihenfolge zu initialisieren, nimmt es den ersten Wert (der ist1
) und setzt diesen aufa[0]
. Es ist, alsint a[5] = { a[2] };
würde es bleiben, wo wir schon sinda[2] = 1
. Das resultierende Array lautet jetzt:1 0 1 0 0
Ein anderes Beispiel:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Obwohl die Reihenfolge etwas willkürlich ist und von links nach rechts geht, würde sie in diesen 6 Schritten ablaufen:quelle
A = B = C = 5
ist keine Deklaration (oder Initialisierung). Es ist ein normaler Ausdruck, der analysiert wird,A = (B = (C = 5))
weil der=
Operator richtig assoziativ ist. Das hilft nicht wirklich bei der Erklärung, wie die Initialisierung funktioniert. Das Array beginnt tatsächlich zu existieren, wenn der Block eingegeben wird, in dem es definiert ist. Dies kann lange dauern, bis die eigentliche Definition ausgeführt wird.a[2] = 1
Initialisiererausdrucks angewendet wird? Das beobachtete Ergebnis ist so, als ob es wäre, aber der Standard scheint nicht zu spezifizieren, dass dies der Fall sein sollte. Das ist das Zentrum der Kontroverse, und diese Antwort übersieht sie völlig.Die Zuweisung
a[2]= 1
ist ein Ausdruck, der den Wert hat1
, und Sie haben im Wesentlichen geschriebenint a[5]= { 1 };
(mit dem Nebeneffekt, der ebenfallsa[2]
zugewiesen wird1
).quelle
Ich glaube das
int a[5]={ a[2]=1 };
ist ein gutes Beispiel für einen Programmierer, der sich in seinen eigenen Fuß schießt.Ich könnte versucht sein zu glauben, dass Sie das gemeint haben
int a[5]={ [2]=1 };
sich um ein C99-Initialisierungs-Einstellungselement von 2 zu 1 und den Rest zu Null handelt.In dem seltenen Fall, den Sie wirklich wirklich gemeint
int a[5]={ 1 }; a[2]=1;
haben, wäre das eine lustige Art, es zu schreiben. Auf jeden Fall läuft Ihr Code darauf hinaus, obwohl einige hier darauf hingewiesen haben, dass er nicht genau definiert ist, wann das Schreibena[2]
tatsächlich ausgeführt wird. Die Falle hierbei ist, dassa[2]=1
es sich nicht um einen bestimmten Initialisierer handelt, sondern um eine einfache Zuweisung, die selbst den Wert 1 hat.quelle