Ist es möglich, einen C-Zeiger auf NULL zu initialisieren?


90

Ich hatte Dinge wie geschrieben

char *x=NULL;

unter der Annahme, dass

 char *x=2;

würde einen charZeiger auf Adresse 2 erstellen .

Im Programmier-Tutorial für GNU C heißt es jedoch, dass int *my_int_ptr = 2;der ganzzahlige Wert 2bei der my_int_ptrZuweisung an eine beliebige zufällige Adresse gespeichert wird.

Dies scheint zu implizieren, dass mein eigener char *x=NULLWert NULLeiner charbeliebigen Adresse im Speicher zuweist, was auch immer der Wert von cast für a ist.

Während

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *x=NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

druckt in der Tat

ist Null

Wenn ich es kompiliere und ausführe, mache ich mir Sorgen, dass ich mich auf undefiniertes oder zumindest unterbestimmtes Verhalten verlasse und schreiben sollte

char *x;
x=NULL;

stattdessen.


72
Es gibt einen sehr verwirrenden Unterschied zwischen dem, was int *x = whatever;tut und dem, was int *x; *x = whatever;tut. int *x = whatever;verhält sich eigentlich so int *x; x = whatever;, nicht *x = whatever;.
user2357112 unterstützt Monica

78
Dieses Tutorial scheint diese verwirrende Unterscheidung falsch verstanden zu haben.
user2357112 unterstützt Monica

51
So viele beschissene Tutorials im Web! Hör sofort auf zu lesen. Wir brauchen wirklich eine SO schwarze Liste, auf der wir beschissene Bücher öffentlich beschämen können ...
Lundin

9
@MM Was es im Jahr 2017 nicht weniger beschissen macht. Angesichts der Entwicklung von Compilern und Computern seit den 80er Jahren ist es im Grunde dasselbe, als wäre ich Arzt und würde Medizinbücher lesen, die im 18. Jahrhundert geschrieben wurden.
Lundin

13
Ich glaube nicht, dass dieses Tutorial als " Das GNU C-Programmier-Tutorial" qualifiziert ist ...
marcelm

Antworten:


114

Ist es möglich, einen C-Zeiger auf NULL zu initialisieren?

TL; DR Ja, sehr.


Die tatsächliche Behauptung auf dem Leitfaden lautet wie folgt

Wenn Sie jedoch nur die einzelne Anfangszuweisung verwenden, int *my_int_ptr = 2;versucht das Programm, den Inhalt des Speicherorts, auf den durch gezeigt wird, my_int_ptrmit dem Wert 2 zu füllen . Da er my_int_ptrmit Müll gefüllt ist, kann es sich um eine beliebige Adresse handeln. [...]

Nun, sie sind falsch, du hast recht.

Für die Aussage ( ignoriert vorerst die Tatsache, dass der Zeiger auf die Ganzzahlkonvertierung ein implementierungsdefiniertes Verhalten ist )

int * my_int_ptr = 2;

my_int_ptr ist eine Variable (vom Typ Zeiger auf int ), sie hat eine eigene Adresse (Typ: Adresse des Zeigers auf Ganzzahl), Sie speichern einen Wert von 2in dieser Adresse.

Da my_int_ptres sich nun um einen Zeigertyp handelt, können wir sagen, dass er auf den Wert von "Typ" an der Speicherstelle zeigt, auf die gezeigt wird die der Wert zeigt, inmy_int_ptr . Also, Sie sind im Wesentlichen den Wert Zuordnung von der Zeigervariable, nicht der Wert des Speicherplatzes , auf den durch den Zeiger.

Also zum Abschluss

 char *x=NULL;

initialisiert die Zeigervariable xaufNULL , nicht auf den Wert an der Speicheradresse, auf die der Zeiger zeigt .

Dies ist das gleiche wie

 char *x;
 x = NULL;    

Erweiterung:

Nun, streng konform, eine Aussage wie

 int * my_int_ptr = 2;

ist illegal, da es sich um eine Verletzung von Einschränkungen handelt. Deutlich sein,

  • my_int_ptr ist eine Zeigervariable vom Typ int *
  • Eine Ganzzahlkonstante 2hat intper Definition einen Typ .

und da es sich nicht um "kompatible" Typen handelt, ist diese Initialisierung ungültig, da sie gegen die Regeln der einfachen Zuweisung verstößt, die in Kapitel 6.5.16.1 / P1, beschrieben in Lundins Antwort, erwähnt werden .

Falls jemand interessiert ist, wie die Initialisierung mit einfachen Zuweisungsbeschränkungen verbunden ist, zitieren Sie C11, Kapitel §6.7.9, P11

Der Initialisierer für einen Skalar muss ein einzelner Ausdruck sein, der optional in geschweiften Klammern eingeschlossen ist. Der Anfangswert des Objekts ist der des Ausdrucks (nach der Konvertierung). Es gelten die gleichen Typbeschränkungen und Konvertierungen wie für die einfache Zuweisung, wobei der Typ des Skalars als nicht qualifizierte Version seines deklarierten Typs angesehen wird.


@ Random832n Sie sind falsch. Ich habe den entsprechenden Teil in meiner Antwort zitiert. Bitte korrigieren Sie mich, wenn nicht anders angegeben. Oh, und die Betonung ist absichtlich.
Sourav Ghosh

"... ist illegal, da es sich um eine Verletzung von Einschränkungen handelt. ... ein ganzzahliges Literal, 2 hat per Definition den Typ int." ist problematisch. Es klingt wie weil 2ist ein int, die Zuordnung ist ein Problem. Aber es ist mehr als das. NULLkann auch ein int, ein sein int 0. Es ist nur so, dass char *x = 0;es gut definiert ist und char *x = 2;nicht. 6.3.2.3 Zeiger 3 (Übrigens: C definiert kein ganzzahliges Literal , nur String-Literal und zusammengesetztes Literal . 0
Ist

@chux Du bist sehr korrekt, aber ist es nicht so char *x = (void *)0;, dass du dich anpasst ? oder ist es nur mit anderen Ausdrücken, die den Wert ergibt 0?
Sourav Ghosh

10
@SouravGhosh: Ganzzahlkonstanten mit Wert 0sind etwas Besonderes: Sie konvertieren implizit in Nullzeiger, getrennt von den üblichen Regeln zum expliziten Umwandeln allgemeiner Ganzzahlausdrücke in Zeigertypen.
Steve Jessop

1
Die im C-Referenzhandbuch von 1974 beschriebene Sprache erlaubte es Deklarationen nicht, Initialisierungsausdrücke anzugeben, und das Fehlen solcher Ausdrücke macht die Verwendung von "Deklarationsspiegeln" viel praktischer. Die Syntax int *p = somePtrExpressionist meiner Meinung nach ziemlich schrecklich, da es so aussieht, als würde der Wert von festgelegt, *paber tatsächlich wird der Wert von festgelegt p.
Supercat

53

Das Tutorial ist falsch. In ISO C int *my_int_ptr = 2;ist ein Fehler. In GNU C bedeutet dies dasselbe wie int *my_int_ptr = (int *)2;. Dies konvertiert die Ganzzahl 2in eine Speicheradresse, wie vom Compiler festgelegt.

Es wird nicht versucht, etwas an dem von dieser Adresse adressierten Ort (falls vorhanden) zu speichern. Wenn Sie weiter schreiben würden *my_int_ptr = 5;, würde es versuchen, die Nummer 5an dem von dieser Adresse adressierten Ort zu speichern .


1
Ich wusste nicht, dass die Konvertierung von Ganzzahlen in Zeiger implementiert ist. Danke für die Auskunft.
Aufgabe am

1
@taskinoor Bitte beachten Sie, dass es nur dann eine Konvertierung gibt, wenn Sie sie durch eine Besetzung erzwingen, wie in dieser Antwort. Wenn nicht für die Besetzung, sollte der Code nicht kompiliert werden.
Lundin

2
@taskinoor: Ja, die verschiedenen Konvertierungen in C sind ziemlich verwirrend. Dieses Q enthält interessante Informationen zu Conversions: C: Wann ist das Umwandeln zwischen Zeigertypen kein undefiniertes Verhalten? .
Sleske

17

Um zu verdeutlichen, warum das Tutorial falsch ist, int *my_int_ptr = 2; handelt es sich um eine "Einschränkungsverletzung". Es handelt sich um Code, der nicht kompiliert werden darf, und der Compiler muss Ihnen bei Auftreten eine Diagnose stellen.

Gemäß 6.5.16.1 Einfache Zuordnung:

Einschränkungen

Eine der folgenden Bedingungen gilt:

  • Der linke Operand hat einen atomaren, qualifizierten oder nicht qualifizierten arithmetischen Typ, und der rechte hat einen arithmetischen Typ.
  • Der linke Operand hat eine atomare, qualifizierte oder nicht qualifizierte Version einer Struktur oder eines Vereinigungstyps, der mit dem Typ des rechten kompatibel ist.
  • Der linke Operand hat einen atomaren, qualifizierten oder nicht qualifizierten Zeigertyp, und (unter Berücksichtigung des Typs, den der linke Operand nach der Wertkonvertierung haben würde) sind beide Operanden Zeiger auf qualifizierte oder nicht qualifizierte Versionen kompatibler Typen, und der Typ, auf den der linke zeigt, hat alle die Qualifikanten des Typs, auf den rechts hingewiesen wird;
  • Der linke Operand hat einen atomaren, qualifizierten oder nicht qualifizierten Zeigertyp, und (unter Berücksichtigung des Typs, den der linke Operand nach der Wertkonvertierung haben würde) ist ein Operand ein Zeiger auf einen Objekttyp und der andere ein Zeiger auf eine qualifizierte oder nicht qualifizierte Version von nichtig, und der Typ, auf den links gezeigt wird, enthält alle Qualifikationsmerkmale des Typs, auf den rechts gezeigt wird;
  • Der linke Operand ist ein atomarer, qualifizierter oder nicht qualifizierter Zeiger, und der rechte ist eine Nullzeigerkonstante. oder
  • Der linke Operand hat den Typ atomar, qualifiziert oder nicht qualifiziert _Bool, und der rechte ist ein Zeiger.

In diesem Fall ist der linke Operand ein nicht qualifizierter Zeiger. Nirgends wird erwähnt, dass der richtige Operand eine ganze Zahl (arithmetischer Typ) sein darf. Der Code verstößt also gegen den C-Standard.

Es ist bekannt, dass sich GCC schlecht verhält, es sei denn, Sie weisen ausdrücklich an, dass es sich um einen Standard-C-Compiler handelt. Wenn Sie den Code als kompilieren -std=c11 -pedantic-errors, wird eine Diagnose korrekt angezeigt, wie dies erforderlich ist.


4
für das Vorschlagen von -pedantic-Fehlern gestimmt. Obwohl ich wahrscheinlich die verwandte -Wpedantic verwenden werde.
Fagricipni

2
Eine Ausnahme von Ihrer Aussage, dass der richtige Operand keine Ganzzahl sein darf: In Abschnitt 6.3.2.3 heißt es: „Ein ganzzahliger Konstantenausdruck mit dem Wert 0 oder ein solcher Ausdruck, der in einen Typ umgewandelt wird void *, wird als Nullzeigerkonstante bezeichnet.“ Beachten Sie den vorletzten Aufzählungspunkt in Ihrem Angebot. Daher int* p = 0;ist eine legale Art zu schreiben int* p = NULL;. Obwohl letzteres klarer und konventioneller ist.
Davislor

1
Das macht die pathologische Verschleierung auch int m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;legal.
Davislor

@Davislor, der durch Punkt 5 im Standardzitat in dieser Antwort abgedeckt ist (stimmen Sie zu, dass die Zusammenfassung danach wahrscheinlich aber erwähnen sollte)
MM

1
@chux Ich glaube, ein wohlgeformtes Programm müsste einen intptr_texplizit in einen der zulässigen Typen auf der rechten Seite konvertieren . Das heißt, void* a = (void*)(intptr_t)b;ist nach Punkt 4 legal, (intptr_t)bist jedoch weder ein kompatibler Zeigertyp noch eine void*oder eine Nullzeigerkonstante und void* aist weder ein arithmetischer Typ noch _Bool. Der Standard besagt, dass die Konvertierung legal ist, aber nicht, dass sie implizit ist.
Davislor

15

int *my_int_ptr = 2

speichert den ganzzahligen Wert 2 an einer beliebigen zufälligen Adresse in my_int_ptr, wenn diese zugewiesen wird.

Das ist völlig falsch. Wenn dies tatsächlich geschrieben ist, besorgen Sie sich bitte ein besseres Buch oder Tutorial.

int *my_int_ptr = 2Definiert einen ganzzahligen Zeiger, der auf Adresse 2 zeigt. Wenn Sie versuchen, auf die Adresse zuzugreifen, kommt es höchstwahrscheinlich zu einem Absturz 2.

*my_int_ptr = 2, dh ohne das intin der Zeile, speichert den Wert zwei an einer beliebigen zufälligen Adresse my_int_ptr, auf die gezeigt wird. Nachdem Sie dies gesagt haben, können Sie NULLeinem Zeiger zuweisen , wenn er definiert ist. char *x=NULL;ist vollkommen gültig C.

Bearbeiten: Während ich dies schrieb, wusste ich nicht, dass die Konvertierung von Ganzzahlen in Zeiger ein implementierungsdefiniertes Verhalten ist. Weitere Informationen finden Sie in den guten Antworten von @MM und @SouravGhosh.


1
Es ist völlig falsch, weil es sich um eine Einschränkungsverletzung handelt, nicht aus einem anderen Grund. Dies ist insbesondere falsch: "int * my_int_ptr = 2 definiert einen ganzzahligen Zeiger, der auf Adresse 2 zeigt".
Lundin

@Lundin: Dein Satz "nicht aus irgendeinem anderen Grund" ist selbst falsch und irreführend. Wenn Sie das Problem mit der Typkompatibilität beheben, bleibt die Tatsache bestehen, dass der Autor des Lernprogramms die Funktionsweise von Zeigerinitialisierungen und -zuweisungen grob falsch darstellt.
Leichtigkeitsrennen im Orbit

14

Eine Menge Verwirrung über C-Zeiger kommt von einer sehr schlechten Wahl, die ursprünglich in Bezug auf den Codierungsstil getroffen wurde, was durch eine sehr schlechte kleine Wahl in der Syntax der Sprache bestätigt wird.

int *x = NULL;ist richtig C, aber es ist sehr irreführend, ich würde sogar unsinnig sagen, und es hat das Verständnis der Sprache für viele Anfänger behindert. Man denkt, wir könnten später etwas tun, *x = NULL;was natürlich unmöglich ist. Sie sehen, der Typ der Variablen ist nicht intund der Name der Variablen ist nicht *x, noch spielt die *in der Deklaration eine funktionale Rolle in Zusammenarbeit mit der =. Es ist rein deklarativ. Was also viel sinnvoller ist, ist Folgendes:

int* x = NULL;Das ist auch richtig C, obwohl es nicht dem ursprünglichen K & R-Codierungsstil entspricht. Es macht vollkommen klar, dass der Typ int*und die Zeigervariable ist x, so dass selbst für den Uneingeweihten, in dem der Wert NULLgespeichert wird x, der ein Zeiger ist, deutlich wirdint .

Darüber hinaus ist es einfacher, eine Regel abzuleiten: Wenn der Stern vom Variablennamen entfernt ist, handelt es sich um eine Deklaration, während der an den Namen angehängte Stern eine Zeiger-Dereferenzierung darstellt.

Jetzt wird es viel verständlicher, dass wir es weiter unten entweder tun können, x = NULL;oder *x = 2;mit anderen Worten, es macht es für einen Anfänger einfacher zu sehen, wie es variable = expressionzu pointer-type variable = pointer-expressionund führtdereferenced-pointer-variable = expression . (Für den Eingeweihten meine ich mit 'Ausdruck' 'Wert'.)

Die unglückliche Wahl in der Syntax der Sprache ist, dass Sie beim Deklarieren lokaler Variablen sagen können, int i, *p;welche eine Ganzzahl und einen Zeiger auf eine Ganzzahl deklarieren, sodass man glaubt, dass dies *ein nützlicher Teil des Namens ist. Dies ist jedoch nicht der Fall, und diese Syntax ist nur ein eigenartiger Sonderfall, der der Einfachheit halber hinzugefügt wurde. Meiner Meinung nach hätte sie niemals existieren dürfen, da sie die oben vorgeschlagene Regel ungültig macht. Soweit ich weiß, ist diese Syntax nirgendwo anders in der Sprache von Bedeutung, aber selbst wenn dies der Fall ist, weist sie auf eine Diskrepanz bei der Definition von Zeigertypen in C hin. Überall sonst, in Deklarationen mit einzelnen Variablen, in Parameterlisten, In Strukturelementen usw. können Sie Ihre Zeiger als type* pointer-variableanstelle von deklarieren type *pointer-variable. es ist vollkommen legal und macht mehr Sinn.


int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,... Ich muss zustimmen, nicht zuzustimmen. It makes one think.... hör auf zu denken, lies zuerst ein C-Buch, nichts für ungut.
Sourav Ghosh

^^ das hätte für mich vollkommen Sinn gemacht. Ich nehme an, es ist subjektiv.
Mike Nakis

5
@SouravGhosh Als Ansichtssache denke ich , dass C sollte so konzipiert wurde, dass int* somePtr, someotherPtrzwei Zeiger erklärt, in der Tat, habe ich zu schreiben , int* somePtraber das führt zu dem Fehler , den Sie beschreiben.
Fagricipni

1
@fagricipni Aus diesem Grund habe ich die Verwendung der Deklarationssyntax für mehrere Variablen eingestellt. Ich deklariere meine Variablen einzeln. Wenn ich sie wirklich in derselben Zeile haben möchte, trenne ich sie eher durch Semikolons als durch Kommas. "Wenn ein Ort schlecht ist, gehe nicht zu diesem Ort."
Mike Nakis

2
@fagricipni Nun, wenn ich Linux von Grund auf neu hätte entwerfen können, hätte ich createstattdessen verwendet creat. :) Der Punkt ist, es ist wie es ist und wir müssen uns formen, um uns daran anzupassen. Es läuft alles auf die persönliche Wahl am Ende des Tages hinaus, stimme zu.
Sourav Ghosh

6

Ich möchte den vielen ausgezeichneten Antworten etwas Orthogonales hinzufügen. Tatsächlich ist das Initialisieren auf NULLkeine schlechte Praxis und kann nützlich sein, wenn dieser Zeiger zum Speichern eines dynamisch zugewiesenen Speicherblocks verwendet werden kann oder nicht.

int * p = NULL;
...
if (...) {
    p = (int*) malloc(...);
    ...
}
...
free(p);

Da gemäß der Norm ISO-IEC 9899 free ein Nein ist, wenn das Argument lautet NULL, ist der obige Code (oder etwas Bedeutenderes in der gleichen Richtung) legitim.


5
Es ist überflüssig, das Ergebnis von malloc in C umzuwandeln, es sei denn, dieser C-Code sollte auch als C ++ kompiliert werden.
Katze

Sie haben Recht, das void*wird nach Bedarf konvertiert. Code, der mit einem C- und einem C ++ - Compiler funktioniert, kann jedoch Vorteile haben.
Luca Citi

1
@LucaCiti C und C ++ sind verschiedene Sprachen. Es warten nur Fehler auf Sie, wenn Sie versuchen, eine für eine geschriebene Quelldatei mit einem für die andere entwickelten Compiler zu kompilieren. Es ist wie der Versuch, C-Code zu schreiben, den Sie mit Pascal-Tools kompilieren können.
Evil Dog Pie

1
Guter Rat. Ich (versuche) immer meine Zeigerkonstanten auf etwas zu initialisieren. In modernem C kann dies normalerweise ihr Endwert sein und es können constZeiger sein, die in medias res deklariert sind , aber selbst wenn ein Zeiger veränderbar sein muss (wie einer, der in einer Schleife oder von verwendet wird realloc()), wird er so eingestellt, dass er NULLFehler abfängt, wo er zuvor verwendet wurde es ist mit seinem realen Wert festgelegt. Auf den meisten Systemen NULLverursacht die Dereferenzierung an der Fehlerstelle einen Segfault (obwohl es Ausnahmen gibt), während ein nicht initialisierter Zeiger Müll enthält und das Schreiben in ihn beliebigen Speicher beschädigt.
Davislor

1
Außerdem ist es im Debugger sehr leicht zu erkennen, dass ein Zeiger enthält NULL, aber es kann sehr schwierig sein, einen Müllzeiger von einem gültigen zu unterscheiden. Es ist daher hilfreich sicherzustellen, dass alle Zeiger immer entweder gültig sind oder NULLab dem Zeitpunkt der Deklaration.
Davislor


1

Das ist richtig.

int main()
{
    char * x = NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

Diese Funktion ist korrekt für das, was sie tut. Es weist dem Zeichenzeiger x die Adresse 0 zu. Das heißt, es zeigt den Zeiger x auf die Speicheradresse 0.

Alternative:

int main()
{
    char* x = 0;

    if ( !x )
        printf(" x points to NULL\n");

    return EXIT_SUCCESS;
}

Meine Vermutung, was Sie wollten, ist:

int main()
{
    char* x = NULL;
    x = alloc( sizeof( char ));
    *x = '2';

    if ( *x == '2' )
        printf(" x points to an address/location that contains a '2' \n");

    return EXIT_SUCCESS;
}

x is the street address of a house. *x examines the contents of that house.

"Es weist dem Zeichenzeiger x die Adresse 0 zu." -> Vielleicht. C gibt den Wert des Zeigers nicht an, nur das char* x = 0; if (x == 0)ist wahr. Zeiger sind nicht unbedingt ganze Zahlen.
chux

Der Zeiger x zeigt nicht auf die Speicheradresse 0. Der Zeigerwert wird auf einen nicht angegebenen ungültigen Wert gesetzt, der durch Vergleichen mit 0 oder NULL getestet werden kann . Die eigentliche Operation ist implementierungsdefiniert. Hier gibt es nichts, was die eigentliche Frage beantwortet.
Marquis von Lorne
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.