Jaka jest różnica między NULL, „\ 0” i 0?


309

W C, pojawiają się różnice pomiędzy różnymi wartościami Zero - NULL, NULi 0.

Wiem, że znak ASCII '0'ocenia na 48lub 0x30.

NULLWskaźnik jest zazwyczaj definiowane jako:

#define NULL 0

Lub

#define NULL (void *)0

Ponadto istnieje NULpostać, '\0'która wydaje się również oceniać 0.

Czy zdarza się, że te trzy wartości nie mogą być równe?

Czy dotyczy to również systemów 64-bitowych?


1
Zobacz stackoverflow.com/questions/176989/..., aby uzyskać więcej informacji na temat różnic między 0 a NULL.
David Rodríguez - dribeas

7
Identyfikator NULnie istnieje w standardowym języku lub bibliotece C (lub, o ile wiem, w C ++). Znak null jest czasami nazywany NUL, ale jest to C lub C ++, które zwykle określa się mianem '\0'.
Keith Thompson

Odpowiedzi:


351

Uwaga: Ta odpowiedź dotyczy języka C, a nie C ++.


Wskaźniki zerowe

Stała literałowa liczby całkowitej 0ma różne znaczenia w zależności od kontekstu, w którym jest używana. We wszystkich przypadkach jest to wciąż liczba całkowita z wartością0 , jest ona opisana na różne sposoby.

Jeśli wskaźnik jest porównywany ze stałym literałem 0, jest to sprawdzenie, czy wskaźnik jest wskaźnikiem zerowym. Jest 0to następnie określane jako stała zerowego wskaźnika. Standard C definiuje, że 0rzut na ten typ void *jest zarówno wskaźnikiem zerowym, jak i stałą wskaźnika zerowego.

Dodatkowo, aby poprawić czytelność, makro NULLznajduje się w pliku nagłówkowym stddef.h. W zależności od kompilatora może być możliwe #undef NULLi przedefiniowanie go do czegoś zwariowanego.

Dlatego oto kilka prawidłowych sposobów sprawdzania pustego wskaźnika:

if (pointer == NULL)

NULLjest zdefiniowany do porównania równego zerowemu wskaźnikowi. Jest to implementacja zdefiniowana jako rzeczywista definicja NULL, pod warunkiem, że jest to poprawna stała wskaźnika zerowego.

if (pointer == 0)

0 jest kolejną reprezentacją stałej wskaźnika zerowego.

if (!pointer)

To ifstwierdzenie domyślnie sprawdza, że ​​„nie jest 0”, więc odwracamy to, aby znaczyło „wynosi 0”.

Poniżej przedstawiono NIEPRAWIDŁOWE sposoby sprawdzania pustego wskaźnika:

int mynull = 0;
<some code>
if (pointer == mynull)

Dla kompilatora nie jest to sprawdzenie wskaźnika zerowego, ale sprawdzenie równości dwóch zmiennych. Może to działać, jeśli mynull nigdy się nie zmienia w kodzie, a stała optymalizacji kompilatora składa wartość 0 w instrukcji if, ale nie jest to gwarantowane, a kompilator musi wygenerować co najmniej jeden komunikat diagnostyczny (ostrzeżenie lub błąd) zgodnie ze standardem C.

Zauważ, że czym jest wskaźnik zerowy w języku C. Nie ma to znaczenia dla podstawowej architektury. Jeśli architektura bazowa ma zerową wartość wskaźnika zdefiniowaną jako adres 0xDEADBEEF, to kompilator musi rozwiązać ten problem.

Jako takie, nawet w tej zabawnej architekturze, następujące sposoby są nadal poprawnymi sposobami sprawdzania pustego wskaźnika:

if (!pointer)
if (pointer == NULL)
if (pointer == 0)

Poniżej przedstawiono NIEPRAWIDŁOWE sposoby sprawdzania pustego wskaźnika:

#define MYNULL (void *) 0xDEADBEEF
if (pointer == MYNULL)
if (pointer == 0xDEADBEEF)

ponieważ są one postrzegane przez kompilator jako zwykłe porównania.

Brak znaków

'\0'jest zdefiniowany jako znak zerowy - jest to znak ze wszystkimi bitami ustawionymi na zero. To nie ma nic wspólnego ze wskaźnikami. Jednak możesz zobaczyć coś podobnego do tego kodu:

if (!*string_pointer)

sprawdza, czy wskaźnik łańcucha wskazuje na znak zerowy

if (*string_pointer)

sprawdza, czy wskaźnik łańcucha wskazuje na znak inny niż null

Nie myl ich ze wskaźnikami zerowymi. Tylko dlatego, że reprezentacja bitów jest taka sama, a to pozwala na pewne wygodne przypadki krzyżowania, nie są to tak naprawdę same.

Dodatkowo '\0'jest (podobnie jak wszystkie literały znakowe) stałą całkowitą, w tym przypadku o wartości zero. Więc '\0'jest całkowicie równoważna ozdób 0stałej liczby całkowitej - jedyną różnicą jest intencją , że przenosi się do ludzkiego czytelnika ( „Używam tego jako znak NULL.”).

Bibliografia

Więcej informacji znajduje się w pytaniu 5.3 w comp.lang.c FAQ . Zobacz ten pdf dla standardu C. Sprawdź sekcje 6.3.2.3 Wskaźniki, akapit 3.


3
Dziękujemy za wskazanie listy najczęściej zadawanych pytań. Jednak patrz również c-faq.com/null/nullor0.html
Sinan Ünür 18.08.2009

4
Nie, nie będziesz porównywać ptrdo zera wszystkich bitów . To nie jest memcmp, ale jest to porównanie przy użyciu wbudowanego operatora. Jedna strona to wskaźnik zerowy '\0', a druga to wskaźnik. Podobnie jak w przypadku pozostałych dwóch wersji z NULLi 0. Ci trzej robią to samo.
Johannes Schaub - litb

6
Bierzesz wbudowany operator porównania jako rzecz, która porównywałaby ciągi bitowe. Ale to nie to. Porównuje dwie wartości, które są pojęciami abstrakcyjnymi. Więc wskaźnik NULL że wewnętrznie jest reprezentowane 0xDEADBEEFjest jeszcze wskaźnik null, bez względu na jej łańcuch bitów wygląda i będzie nadal porównać równe NULL, 0, \0a wszystkie inne formy stałe wskaźnik NULL.
Johannes Schaub - litb

2
Masz rację na temat operatora porównania. Odświeżyłem na C99. Mówi: „Wyrażenie stałe liczby całkowitej o wartości 0 lub takie wyrażenie rzutowane na typ void * nazywa się stałą wskaźnika zerowego”. Mówi także, że literał znakowy jest ciągłym wyrażeniem całkowitym. Tak więc, mając własność przechodnią, masz rację ptr == '\0'.
Andrew Keeton,

2
„... może być możliwe #undef NULL i przedefiniowanie go na coś zwariowanego. Każdy, kto to robi, zasługuje na zastrzelenie.” mój dobry pan rozśmieszył mnie głośno ...
oggiemc

34

Wygląda na to, że wiele osób nie rozumie, jakie są różnice między NULL, „\ 0” i 0. Tak więc, aby wyjaśnić i starając się uniknąć powtarzania rzeczy, powiedziano wcześniej:

Stałe wyrażenie typu into wartości 0 lub wyrażenie tego typu rzutowane na typ void *jest stałą wskaźnika zerowego , która po przekształceniu w wskaźnik staje się wskaźnikiem zerowym . Standard gwarantuje, że porównanie będzie nierówne z dowolnym wskaźnikiem do dowolnego obiektu lub funkcji .

NULLto makro zdefiniowane jako stała zerowego wskaźnika .

\0to konstrukcja używana do reprezentowania znaku zerowego , używana do zakończenia łańcucha.

Znak null to bajt, którego wszystkie bity są ustawione na 0.


14

Wszystkie trzy definiują znaczenie zera w innym kontekście.

  • kontekst wskaźnika - używana jest wartość NULL, co oznacza, że ​​wartość wskaźnika wynosi 0, niezależnie od tego, czy jest to 32-bitowy, czy 64-bitowy (jeden przypadek 4 bajty, pozostałe 8 bajtów zer).
  • ciąg znaków - znak reprezentujący cyfrę zero ma wartość szesnastkową 0x30, podczas gdy znak NUL ma wartość szesnastkową 0x00 (używaną do kończenia ciągów).

Te trzy są zawsze różne, gdy spojrzysz na pamięć:

NULL - 0x00000000 or 0x00000000'00000000 (32 vs 64 bit)
NUL - 0x00 or 0x0000 (ascii vs 2byte unicode)
'0' - 0x20

Mam nadzieję, że to wyjaśnia.


8
Nasko: oceń sizeof('\0')i zaskocz się.
caf

3
@Nasko: Byłem naprawdę zaskoczony: gcc, w C: sizeof ('\ 0') == sizeof ('a') == 4, podczas gdy g ++, w C ++: sizeof ('\ 0') == sizeof ('a') == 1
David Rodríguez - dribeas

1
@Nasko: Ze standardu C (wersja robocza, n1124): „Stała znakowa liczby całkowitej ma typ int”, dlatego „\ 0” jest w rzeczywistości typu int w C, a zatem sizeof („\ 0”) wynosi 4 w mojej architekturze (linux, 32bit)
David Rodríguez - dribeas

@dribeas - Nie opisywałem tego jako stałej, a raczej tego, co zobaczyłbyś jako część łańcucha. Zdecydowanie mogłem wyrazić to jasno. Dzięki
Nasko

@ DavidRodríguez-dribeas Undid edit „Poprawiono wartość ASCII„ 0 ”na 0x20 (dec 32)”
chux - Przywróć Monikę

6

Jeśli NULL i 0 są równoważne jako stałe wskaźnika zerowego, co powinienem zastosować? na liście C FAQ dotyczy również tego problemu:

Programiści C muszą to zrozumieć NULLi 0są wymienne w kontekstach wskaźnikowych oraz że niezakodowany 0 jest całkowicie akceptowalny. Każde użycie NULL (w przeciwieństwie do 0) powinno być uważane za delikatne przypomnienie o zaangażowaniu wskaźnika; programiści nie powinni na tym polegać (ani dla własnego zrozumienia, ani dla kompilatora) w odróżnianiu wskaźników 0od liczb całkowitych 0.

Tylko w kontekstach wskaźnikowych są NULLi 0są równoważne.NULLnie należy go używać, gdy 0wymagany jest inny rodzaj , nawet jeśli może on działać, ponieważ spowoduje to wysłanie niewłaściwego komunikatu stylistycznego. (Ponadto ANSI pozwala na definicję NULLby ((void *)0), która w ogóle nie będzie działać w kontekstach nieinterpretacyjnych.) W szczególności nie używaj, NULLgdy NULpożądany jest znak null ASCII ( ). Podaj własną definicję

#define NUL '\0'

Jeśli musisz.


5

Jaka jest różnica między NULL, „\ 0” i 0

„znak zerowy (NUL)” najłatwiej jest wykluczyć. '\0'to dosłowny charakter. W C jest zaimplementowany jako int, więc jest taki sam jak 0, czyli z INT_TYPE_SIZE. W C ++ literał znakowy jest zaimplementowany jako char, który ma 1 bajt. Zwykle różni się to od NULLlub 0.

Następnie NULLjest wartością wskaźnika, która określa, że ​​zmienna nie wskazuje żadnej przestrzeni adresowej. Pomijając fakt, że zwykle jest implementowany jako zera, musi być w stanie wyrazić pełną przestrzeń adresową architektury. Zatem w architekturze 32-bitowej NULL (prawdopodobnie) ma 4 bajty, a w architekturze 64-bitowej 8 bajtów. To zależy od wdrożenia C.

Wreszcie, literał 0jest typu int, który ma rozmiar INT_TYPE_SIZE. Domyślna wartość INT_TYPE_SIZEmoże być różna w zależności od architektury.

Apple napisał:

64-bitowy model danych używany w systemie Mac OS X jest znany jako „LP64”. Jest to powszechny model danych używany przez inne 64-bitowe systemy UNIX od Sun i SGI oraz 64-bitowy Linux. Model danych LP64 definiuje podstawowe typy w następujący sposób:

  • ints są 32-bitowe
  • długie są 64-bitowe
  • długie-długie są również 64-bitowe
  • wskaźniki są 64-bitowe

Wikipedia 64-bit :

Kompilator VC ++ firmy Microsoft używa modelu LLP64.

64-bit data models
Data model short int long  long long pointers Sample operating systems
LLP64      16    32  32    64        64       Microsoft Win64 (X64/IA64)
LP64       16    32  64    64        64       Most Unix and Unix-like systems (Solaris, Linux, etc.)
ILP64      16    64  64    64        64       HAL
SILP64     64    64  64    64        64       ?

Edycja : Dodano więcej literału postaci.

#include <stdio.h>

int main(void) {
    printf("%d", sizeof('\0'));
    return 0;
}

Powyższy kod zwraca 4 w przypadku gcc i 1 w przypadku g ++.


2
Nie, '\0'to nie jest wartość 1-bajtowy. Jest to dosłowny znak, który jest ciągłym wyrażeniem liczb całkowitych - więc jeśli można powiedzieć, że ma rozmiar, to jest to rozmiar int(który musi wynosić co najmniej 2 bajty). Jeśli mi nie wierzysz, oceń sizeof('\0')i przekonaj się sam. '\0', 0I 0x0są w pełni równoważne.
caf

@caf zależy od języka. Jeśli mi nie wierzysz, wypróbuj sizeof('\0')kompilator C ++.
Eugene Yokota,

2
powinieneś używać „% zu” podczas drukowania sizeof (czegoś)
Nieużywane


4

Jeden dobry kawałek, który pomaga mi, gdy zaczynam od C (zaczerpnięte z Expert C Programming by Linden)

One 'l' nul i Two 'l' null

Zapamiętaj ten mały wierszyk, aby przywołać poprawną terminologię wskaźników i ASCII zero:

The one "l" NUL ends an ASCII string,

The two "l" NULL points to no thing.

Apologies to Ogden Nash, but the three "l" nulll means check your spelling. 

Znak ASCII ze wzorem bitowym równym zero jest nazywany „NUL”. Specjalna wartość wskaźnika, która oznacza, że ​​wskaźnik nigdzie nie wskazuje, to „NULL”. Te dwa terminy nie mają znaczenia zamiennego.


Znacznie prostsze: NULjest to kod, takich jak kontrola BEL, VT, HT, SOTitd., A zatem ma max. 3 znaki.
glglgl

2

„NUL” nie jest 0, ale odnosi się do znaku NUL ASCII. Przynajmniej tak to widziałem. Wskaźnik zerowy jest często definiowany jako 0, ale zależy to od środowiska, w którym działasz, oraz specyfikacji używanego systemu operacyjnego lub języka.

W ANSI C wskaźnik zerowy jest określony jako liczba całkowita 0. Zatem każdy świat, w którym to nieprawda, nie jest zgodny z ANSI C.


1

Bajt o wartości 0x00to w tablicy ASCII znak specjalny o nazwie NULlub NULL. W C, ponieważ nie należy osadzać znaków kontrolnych w kodzie źródłowym, jest to reprezentowane w ciągach C ze znakiem ucieczki 0, tj \0.

Ale prawdziwa wartość NULL nie jest jest wartością. To brak wartości. W przypadku wskaźnika oznacza to, że wskaźnik nie ma na co wskazywać. W bazie danych oznacza to, że pole nie ma wartości (co nie jest tym samym, co stwierdzenie, że pole jest puste, 0 lub wypełnione spacjami).

Rzeczywista wartość danego formatu pliku bazy danych systemu lub używa do reprezentuję NULLnie jest koniecznie 0x00.


0

NULLnie ma gwarancji, że wynosi 0 - jego dokładna wartość zależy od architektury. Większość głównych architektur to definiuje (void*)0.

'\0' będzie zawsze równa 0, ponieważ w ten sposób bajt 0 jest kodowany w dosłownym znaku.

Nie pamiętam, czy kompilatory C są wymagane do używania ASCII - jeśli nie, '0'może nie zawsze być równe 48. Niezależnie od tego, jest mało prawdopodobne, że kiedykolwiek spotkasz system, który używa alternatywnego zestawu znaków, takiego jak EBCDIC, chyba że pracujesz nad bardzo niejasne systemy.

Rozmiary różnych typów będą się różnić w systemach 64-bitowych, ale wartości całkowite będą takie same.


Niektórzy komentatorzy wyrazili wątpliwości, czy wartość NULL będzie równa 0, ale nie będzie zerowa. Oto przykładowy program wraz z oczekiwanymi danymi wyjściowymi w takim systemie:

#include <stdio.h>

int main () {
    size_t ii;
    int *ptr = NULL;
    unsigned long *null_value = (unsigned long *)&ptr;
    if (NULL == 0) {
        printf ("NULL == 0\n"); }
    printf ("NULL = 0x");
    for (ii = 0; ii < sizeof (ptr); ii++) {
        printf ("%02X", null_value[ii]); }
    printf ("\n");
    return 0;
}

Ten program może wydrukować:

NULL == 0
NULL = 0x00000001

2
OP pytał o „\ 0” (znak NUL), a nie „0” (znak zero)
Chris Lutz

2
@Chris: „\ 0” nie ma wartości NULL, jest bajtem 0 zakodowanym ósemkowo w literałach znaków.
John Millikin,

2
W C ++ standard gwarantuje, że konwersja z wartości całkowitej 0 na wskaźnik zawsze da wskaźnik zerowy. W C ++ 0 jest gwarantowanym zerowym wskaźnikiem, podczas gdy z drugiej strony NULL jest makrem, a złośliwy programista może przedefiniować go jako coś innego.
David Rodríguez - dribeas

6
A NULL ma gwarantowaną wartość 0. Wzorzec bitowy wskaźnika NULL nie jest gwarantowany jako wszystkie zera, ale stała NULL wynosi i zawsze będzie wynosić 0
czerwiec

2
Twoje pierwsze zdanie jest niepoprawne - NULL nie może być zdefiniowane jako (void *) 0 w C ++, ponieważ nie ma niejawnej konwersji z void * na inny wskaźnik (inaczej niż w C).

-2

(void *) 0 ma wartość NULL, a „\ 0” oznacza koniec ciągu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.