int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
verursacht einen Segfault, während
int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
nicht. Jetzt:
int *nums = {5, 2, 1, 4};
printf("%d\n", nums);
druckt 5.
Auf dieser Grundlage habe ich vermutet, dass die Array-Initialisierungsnotation {} diese Daten blind in die Variable auf der linken Seite lädt. Wenn es int [] ist, wird das Array wie gewünscht gefüllt. Wenn es int * ist, wird der Zeiger mit 5 gefüllt, und die Speicherstellen, nach denen der Zeiger gespeichert ist, werden mit 2, 1 und 4 gefüllt. Num [0] versucht also, 5 zu deref, was einen Segfault verursacht.
Wenn ich falsch liege, korrigieren Sie mich bitte. Und wenn ich richtig liege, erläutern Sie dies bitte, da ich nicht verstehe, warum Array-Initialisierer so funktionieren, wie sie es tun.
-pedantic-errors
Flag auszuführen, und beobachten Sie die Diagnose.int *nums = {5, 2, 1, 4};
ist nicht gültig C.Antworten:
Es gibt eine (dumme) Regel in C, die besagt, dass jede einfache Variable mit einer in Klammern eingeschlossenen Initialisierungsliste initialisiert werden kann, als wäre es ein Array.
Zum Beispiel können Sie schreiben
int x = {0};
, was völlig gleichbedeutend ist mitint x = 0;
.Wenn Sie also schreiben, geben
int *nums = {5, 2, 1, 4};
Sie einer einzelnen Zeigervariablen tatsächlich eine Initialisierungsliste. Da es sich jedoch nur um eine einzelne Variable handelt, wird ihr nur der erste Wert 5 zugewiesen, der Rest der Liste wird ignoriert (eigentlich denke ich nicht, dass Code mit überschüssigen Initialisierern sogar mit einem strengen Compiler kompiliert werden sollte) - dies ist nicht der Fall überhaupt in den Speicher geschrieben werden. Der Code entsprichtint *nums = 5;
. Was bedeutet,nums
sollte auf Adresse zeigen5
.Zu diesem Zeitpunkt sollten Sie bereits zwei Compiler-Warnungen / Fehler erhalten haben:
Und dann stürzt der Code natürlich ab und brennt, da
5
es sich höchstwahrscheinlich nicht um eine gültige Adresse handelt, mit der Sie dereferenzieren dürfennums[0]
.Als Randnotiz sollten Sie
printf
Adressen mit dem%p
Bezeichner verweisen oder auf andere Weise undefiniertes Verhalten aufrufen.Ich bin mir nicht ganz sicher, was Sie hier versuchen, aber wenn Sie einen Zeiger auf ein Array setzen möchten, sollten Sie Folgendes tun:
Oder wenn Sie ein Array von Zeigern erstellen möchten:
BEARBEITEN
Nach einigen Recherchen kann ich sagen, dass die "Initialisierungsliste für überschüssige Elemente" tatsächlich nicht gültig ist. C - es handelt sich um eine GCC-Erweiterung .
Der Standard 6.7.9 Initialisierung sagt (Schwerpunkt Mine):
"Skalartyp" ist ein Standardbegriff, der sich auf einzelne Variablen bezieht, die nicht vom Typ Array, Struktur oder Vereinigung sind (diese werden als "Aggregattyp" bezeichnet).
Im Klartext heißt es im Standard: "Wenn Sie eine Variable initialisieren, können Sie einige zusätzliche Klammern um den Initialisiererausdruck setzen, nur weil Sie können."
quelle
{}
. Im Gegenteil, es erleichtert eine der wichtigsten und bequemsten Redewendungen der C-Sprache -{ 0 }
als universeller Null-Initialisierer. Alles in C kann durch Null initialisiert werden= { 0 }
. Dies ist sehr wichtig für das Schreiben von typunabhängigem Code.{0}
bedeutet dies lediglich, das erste Objekt auf Null zu initialisieren und den Rest der Objekte so zu initialisieren, als ob sie eine statische Speicherdauer hätten. Ich würde sagen, dass dies eher ein Zufall als ein absichtliches Sprachdesign eines "universellen Initialisierers" ist, da{1}
nicht alle Objekte auf 1 initialisiert werden.p = 5;
(keiner der aufgelisteten Fälle wird erfüllt, um dem Zeiger eine Ganzzahl zuzuweisen ); und 6.7.9 / 11 besagt, dass die Einschränkungen für die Zuweisung auch für die Initialisierung verwendet werden.{}
Initiierung von Skalaren speziell für diesen Zweck zulässig ist. Das einzige, was zählt, ist, dass der= { 0 }
Initialisierer garantiert das gesamte Objekt auf Null initialisiert. Genau das machte es zu einem Klassiker und einer der elegantesten Redewendungen der C-Sprache.{1}
mit dem Thema zu tun hat. Niemand hat jemals behauptet,{0}
dass dies0
als Multi-Initialisierer für jedes einzelne Mitglied des Aggregats interpretiert wird .SZENARIO 1
Warum ist dieser eine Fehler?
Sie haben
nums
als Zeiger auf int deklariert - dasnums
soll die Adresse einer ganzen Zahl im Speicher enthalten.Sie haben dann versucht,
nums
ein Array mit mehreren Werten zu initialisieren . Ohne auf viele Details einzugehen, ist dies konzeptionell falsch - es ist nicht sinnvoll, einer Variablen, die einen Wert enthalten soll, mehrere Werte zuzuweisen. In dieser Hinsicht würden Sie genau den gleichen Effekt sehen, wenn Sie dies tun:In beiden Fällen (einem Zeiger oder einer int-Variablen mehrere Werte zuweisen) erhält die Variable dann den ersten Wert
5
, während die verbleibenden Werte ignoriert werden. Dieser Code entspricht, aber Sie erhalten Warnungen für jeden zusätzlichen Wert, der nicht in der Zuweisung enthalten sein soll:warning: excess elements in scalar initializer
.Wenn Sie der Zeigervariablen mehrere Werte zuweisen, wird das Programm beim Zugriff fehlerhaft ausgeführt. Dies
nums[0]
bedeutet, dass Sie alles, was in Adresse 5 gespeichert ist, buchstäblich zurückstellen .nums
In diesem Fall haben Sie keinen gültigen Speicher für den Zeiger zugewiesen .Es ist erwähnenswert, dass es keinen Segfault für den Fall gibt, dass der Variablen int mehrere Werte zugewiesen werden (Sie dereferenzieren hier keinen ungültigen Zeiger).
SZENARIO 2
Dieser ist kein Segfault, da Sie legal ein Array von 4 Ints im Stapel zuweisen.
SZENARIO 3
Dieser Fehler tritt nicht wie erwartet auf, da Sie drucken den Wert des Zeigers selbst - NICHT das, was er dereferenziert (was ein ungültiger Speicherzugriff ist).
Andere
Es ist fast immer zum Segfault verurteilt, wenn Sie
den Wert eines Zeigers festcodierenwie diesen fest (da es die Aufgabe des Betriebssystems ist, zu bestimmen, welcher Prozess auf welchen Speicherort zugreifen kann).Als Faustregel gilt also, immer einen Zeiger auf die Adresse einiger zugeordneter zu initialisieren Variablen , z.
oder,
quelle
int *nums = {5, 2, 1, 4};
ist schlecht geformter Code. Es gibt eine GCC-Erweiterung, die diesen Code wie folgt behandelt:Versuch, einen Zeiger auf die Speicheradresse 5 zu bilden. (Dies scheint mir keine nützliche Erweiterung zu sein, aber ich denke, die Entwicklerbasis möchte es).
Um dieses Verhalten zu vermeiden (oder zumindest eine Warnung zu erhalten), können Sie im Standardmodus kompilieren, z
-std=c11 -pedantic
.Eine alternative Form des gültigen Codes wäre:
Dies zeigt auf ein veränderliches Literal mit der gleichen Speicherdauer wie
nums
. Dieint nums[]
Version ist jedoch im Allgemeinen besser, da weniger Speicherplatz benötigt wird und Sie feststellensizeof
können, wie lang das Array ist.quelle
nums
?nums
eine statische Variable innerhalb einer Funktion deklariert ist, oder wäre der Compiler berechtigt, die Lebensdauer des Arrays auf die des umschließenden Blocks zu beschränken, selbst wenn es einer statischen Variablen zugewiesen wurde?nums
ist ein Zeiger vom Typint
. Sie sollten diesen Punkt also auf einen gültigen Speicherort hinweisen.num[0]
Sie versuchen, einen zufälligen Speicherort und damit den Segmentierungsfehler zu dereferenzieren.Ja, der Zeiger hält den Wert 5 und Sie versuchen, ihn zu dereferenzieren, was auf Ihrem System ein undefiniertes Verhalten ist. (Sieht aus wie
5
als wäre kein gültiger Speicherort auf Ihrem System vorhanden.)Wohingegen
ist eine gültige Deklaration, bei der Sie sagen, dass
nums
es sich um ein Array vom Typ handeltint
und der Speicher basierend auf der Anzahl der während der Initialisierung übergebenen Elemente zugewiesen wird.quelle
int *nums = (int[]){5, 2, 1, 4};
Durch Zuweisung
{5, 2, 1, 4}
Sie weisen 5 zu
nums
(nach einer impliziten Typumwandlung von int zu Zeiger auf int). Wenn Sie es verschieben, rufen Sie den Speicherort unter auf0x5
. Darauf kann Ihr Programm möglicherweise nicht zugreifen.Versuchen
quelle