Pisałem takie rzeczy jak
char *x=NULL;
przy założeniu, że
char *x=2;
utworzy char
wskaźnik do adresu 2.
Ale w samouczku programowania GNU C jest napisane, że int *my_int_ptr = 2;
przechowuje wartość całkowitą 2
do dowolnego losowego adresu, w my_int_ptr
którym jest przydzielona.
Wydawałoby się to sugerować, że moje własne char *x=NULL
przypisuje jakąkolwiek wartość NULL
rzutowania char
do jakiegoś losowego adresu w pamięci.
Podczas
#include <stdlib.h>
#include <stdio.h>
int main()
{
char *x=NULL;
if (x==NULL)
printf("is NULL\n");
return EXIT_SUCCESS;
}
faktycznie drukuje
jest NULL
kiedy go kompiluję i uruchamiam, obawiam się, że polegam na niezdefiniowanym zachowaniu lub przynajmniej niedookreślonym zachowaniu i powinienem napisać
char *x;
x=NULL;
zamiast.
c
pointers
initialization
fagricipni
źródło
źródło
int *x = whatever;
robi, a tym, coint *x; *x = whatever;
robi.int *x = whatever;
faktycznie zachowuje się jakint *x; x = whatever;
nie*x = whatever;
.Odpowiedzi:
TL; DR Tak, bardzo.
Rzeczywisty argument przedstawiony na prowadnicy czyta się jak
Cóż, oni się mylą, masz rację.
W przypadku instrukcji ( pomijając na razie fakt, że wskaźnik na konwersję liczb całkowitych jest zachowaniem zdefiniowanym w implementacji )
int * my_int_ptr = 2;
my_int_ptr
jest zmienną (typu wskaźnik doint
), ma swój własny adres (typ: adres wskaźnika do liczby całkowitej),2
w tym adresie przechowujesz wartość .Teraz,
my_int_ptr
będąc typem wskaźnikowym, możemy powiedzieć, że wskazuje on wartość „type” w lokalizacji pamięci wskazywanej przez wartość przechowywaną wmy_int_ptr
. Tak, jesteś w zasadzie przypisywania wartości o zmiennej wskaźnik, a nie wartość lokalizacji pamięci wskazywanego przez wskaźnik.A więc na zakończenie
char *x=NULL;
inicjalizuje zmienną wskaźnika
x
doNULL
, a nie wartość pod adresem pamięci wskazywanego przez wskaźnik .To jest to samo co
char *x; x = NULL;
Ekspansja:
Teraz, będąc ściśle zgodnym, oświadczenie takie jak
int * my_int_ptr = 2;
jest nielegalne, ponieważ wiąże się z naruszeniem przymusu. Żeby było jasne,
my_int_ptr
jest zmienną wskaźnikową, typint *
2
ma typint
.i nie są to typy „kompatybilne”, więc ta inicjalizacja jest nieważna, ponieważ narusza zasady prostego przypisania, o których mowa w rozdziale §6.5.16.1 / P1, opisane w odpowiedzi .
Jeśli ktoś jest zainteresowany, w jaki sposób inicjalizacja jest powiązana z prostymi ograniczeniami przypisania, cytowanie
C11
, rozdział §6.7.9, P11źródło
2
jestint
, zadanie jest problemem. Ale to coś więcej.NULL
może być również anint
, anint 0
. Po prostu tochar *x = 0;
jest dobrze zdefiniowane, achar *x = 2;
nie jest. 6.3.2.3 Wskaźniki 3 (BTW: C nie definiuje literału liczby całkowitej , tylko literał ciągu i literału złożonego .0
Jest stałą liczbą całkowitą )char *x = (void *)0;
zgodne z zasadami ? czy tylko z innymi wyrażeniami, które dają wartość0
?0
są wyjątkowe: niejawnie konwertują je na wskaźniki o wartości null niezależnie od zwykłych reguł jawnego rzutowania ogólnych wyrażeń całkowitych na typy wskaźników.int *p = somePtrExpression
jest raczej okropna jak IMHO, ponieważ wygląda na to, że ustawia wartość,*p
ale w rzeczywistości ustawia wartośćp
.Samouczek jest zły. W ISO C
int *my_int_ptr = 2;
jest to błąd. W GNU C oznacza to samo coint *my_int_ptr = (int *)2;
. To konwertuje liczbę całkowitą2
na adres pamięci, w pewien sposób określony przez kompilator.Nie próbuje zapisywać niczego w lokalizacji, do której odnosi się ten adres (jeśli istnieje). Gdybyś kontynuował pisanie
*my_int_ptr = 5;
, próbowałby zapisać numer5
w lokalizacji, do której odnosi się ten adres.źródło
Aby wyjaśnić, dlaczego samouczek jest błędny,
int *my_int_ptr = 2;
jest „naruszeniem ograniczeń”, jest to kod, którego nie można kompilować i kompilator musi dać ci diagnostykę po napotkaniu go.Zgodnie z 6.5.16.1 Proste przypisanie:
W tym przypadku lewy operand jest niekwalifikowanym wskaźnikiem. Nigdzie nie wspomniano, że prawy operand może być liczbą całkowitą (typ arytmetyczny). Tak więc kod narusza standard C.
Wiadomo, że GCC zachowuje się słabo, chyba że wyraźnie powiesz, że jest to standardowy kompilator C. Jeśli skompilujesz kod jako
-std=c11 -pedantic-errors
, da on poprawnie diagnostykę, tak jak musi.źródło
void *
nazywa się stałą wskaźnika zerowego”. Zwróć uwagę na przedostatni punkt w ofercie. Dlategoint* p = 0;
jest to legalny sposób pisaniaint* p = NULL;
. Chociaż ta ostatnia jest wyraźniejsza i bardziej konwencjonalna.int m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;
również jest legalne.intptr_t
jawnie przekonwertować na jeden z dozwolonych typów po prawej stronie. Oznacza to, że jestvoid* a = (void*)(intptr_t)b;
to zgodne z punktem 4, ale(intptr_t)b
nie jest ani zgodnym typem wskaźnika, anivoid*
stałą a , ani zerowym wskaźnikiem ivoid* a
nie jest ani typem arytmetycznym, ani_Bool
. Standard mówi, że konwersja jest legalna, ale nie mówi, że jest dorozumiana.int *my_int_ptr = 2
To jest całkowicie błędne. Jeśli to rzeczywiście jest napisane, zdobądź lepszą książkę lub samouczek.
int *my_int_ptr = 2
definiuje wskaźnik całkowity, który wskazuje na adres 2. Najprawdopodobniej nastąpi awaria, jeśli spróbujesz uzyskać dostęp do adresu2
.*my_int_ptr = 2
, tj. bez znakuint
w linii, przechowuje wartość dwa pod dowolnym losowym adresem, na którymy_int_ptr
wskazuje. Mówiąc to, możesz przypisaćNULL
do wskaźnika, gdy jest zdefiniowany.char *x=NULL;
jest całkowicie poprawna C.Edycja: Podczas pisania tego nie wiedziałem, że konwersja liczb całkowitych na wskaźnik jest zachowaniem zdefiniowanym przez implementację. Szczegółowe informacje można znaleźć w dobrych odpowiedziach @MM i @SouravGhosh.
źródło
Wiele nieporozumień związanych ze wskaźnikami C wynika z bardzo złego wyboru, którego pierwotnie dokonano w odniesieniu do stylu kodowania, potwierdzonego bardzo złym, niewielkim wyborem w składni języka.
int *x = NULL;
ma rację C, ale jest bardzo mylące, powiedziałbym nawet, że bezsensowne i utrudniało zrozumienie języka wielu nowicjuszom. Pozwala to pomyśleć, że później moglibyśmy to zrobić,*x = NULL;
co jest oczywiście niemożliwe. Widzisz, typ zmiennej nie jestint
, a nazwa zmiennej nie*x
, ani*
w deklaracji nie odgrywa żadnej funkcjonalnej roli we współpracy z=
. Jest czysto deklaratywna. Więc o wiele bardziej sensowne jest to:int* x = NULL;
co jest również poprawne C, choć nie jest zgodne z oryginalnym stylem kodowania K&R. To doskonale wyjaśnia, że typ jestint*
, a zmienna wskaźnikowax
tak, więc nawet dla niewtajemniczonych staje się jasne, że wartośćNULL
jest przechowywana wx
, czyli wskaźniku doint
.Ponadto ułatwia wyprowadzenie reguły: gdy gwiazda znajduje się z dala od nazwy zmiennej, jest to deklaracja, podczas gdy gwiazda dołączona do nazwy jest dereferencją wskaźnika.
Tak więc teraz staje się o wiele bardziej zrozumiałe, że dalej możemy to zrobić
x = NULL;
lub*x = 2;
innymi słowy, nowicjuszowi łatwiej jest zobaczyć, jakvariable = expression
prowadzi dopointer-type variable = pointer-expression
idereferenced-pointer-variable = expression
. (Dla wtajemniczonych przez „wyrażenie” mam na myśli „wartość r”.)Niefortunnym wyborem w składni języka jest to, że podczas deklarowania zmiennych lokalnych można powiedzieć,
int i, *p;
która deklaruje liczbę całkowitą i wskaźnik do liczby całkowitej, więc prowadzi to do przekonania, że*
jest to użyteczna część nazwy. Ale tak nie jest, a ta składnia to po prostu dziwaczny przypadek specjalny, dodany dla wygody i moim zdaniem nigdy nie powinien istnieć, ponieważ unieważnia regułę, którą zaproponowałem powyżej. O ile mi wiadomo, nigdzie indziej w języku ta składnia nie ma znaczenia, ale nawet jeśli tak jest, wskazuje ona na rozbieżność w sposobie definiowania typów wskaźników w C. Wszędzie indziej, w deklaracjach pojedynczych zmiennych, na listach parametrów, w elementach strukturalnych itp. możesz zadeklarować swoje wskaźniki jakotype* pointer-variable
zamiasttype *pointer-variable
; jest to całkowicie legalne i ma więcej sensu.źródło
int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,
... Muszę się nie zgodzić.It makes one think
.... przestań myśleć, najpierw przeczytaj książkę C, bez obrazy.int* somePtr, someotherPtr
deklarowało dwie wskazówki, w rzeczywistości pisałem,int* somePtr
ale to prowadzi do opisanego przez ciebie błędu.create
zamiast niegocreat
. :) Chodzi o to, że tak jest i musimy się ukształtować, aby się do tego dostosować. Wszystko sprowadza się do osobistego wyboru na koniec dnia, zgadzam się.Do wielu doskonałych odpowiedzi chciałbym dodać coś ortogonalnego. W rzeczywistości inicjalizacja do
NULL
jest daleka od złej praktyki i może być przydatna, jeśli ten wskaźnik może, ale nie musi, być używany do przechowywania dynamicznie przydzielanego bloku pamięci.int * p = NULL; ... if (...) { p = (int*) malloc(...); ... } ... free(p);
Ponieważ zgodnie z normą ISO-IEC 9899
free
jest to nop, gdy argumentem jestNULL
, powyższy kod (lub coś bardziej znaczącego w tych samych liniach) jest prawidłowy.źródło
void*
jest konwertowany w razie potrzeby. Ale posiadanie kodu, który działa z kompilatorem C i C ++, może przynieść korzyści.const
wskaźniki zadeklarowane w medias res , ale nawet gdy wskaźnik musi być zmienny (jak ten używany w pętli lub przezrealloc()
), ustawienie go tak, abyNULL
łapał błędy tam, gdzie był używany wcześniej jest ustawiony z jego prawdziwą wartością. W większości systemów wyłuskiwanie odwołańNULL
powoduje segfault w punkcie awarii (chociaż są wyjątki), podczas gdy niezainicjowany wskaźnik zawiera śmieci, a zapisywanie do nich powoduje uszkodzenie dowolnej pamięci.NULL
, ale może być bardzo trudno odróżnić wskaźnik śmieci od prawidłowego. Dlatego warto upewnić się, że wszystkie wskaźniki są zawsze prawidłowe lubNULL
od momentu deklaracji.to jest wskaźnik zerowy
int * nullPtr = (void*) 0;
źródło
To jest poprawne.
int main() { char * x = NULL; if (x==NULL) printf("is NULL\n"); return EXIT_SUCCESS; }
Ta funkcja jest odpowiednia do tego, co robi. Przypisuje adres 0 do wskaźnika znaku x. Oznacza to, że wskazuje wskaźnik x na adres pamięci 0.
Alternatywny:
int main() { char* x = 0; if ( !x ) printf(" x points to NULL\n"); return EXIT_SUCCESS; }
Domyślam się, czego chciałeś:
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.
źródło
char* x = 0; if (x == 0)
będzie prawdziwe. Wskaźniki niekoniecznie są liczbami całkowitymi.