Czy mogę użyć NULL jako podstawienia wartości 0?

73

Czy mogę używać NULLwskaźnika jako zamiennika wartości 0?

Czy jest w tym coś złego?


Jak na przykład:

int i = NULL;

jako zamiennik dla:

int i = 0;

W ramach eksperymentu skompilowałem następujący kod:

#include <stdio.h>

int main(void)
{
    int i = NULL;
    printf("%d",i);

    return 0;
}

Wynik:

0

Rzeczywiście daje mi to ostrzeżenie, które samo w sobie jest całkowicie poprawne:

warning: initialization makes integer from pointer without a cast [-Wint-conversion] 

ale wynik jest nadal równoważny.


  • Czy przechodzę przez to do „Nieokreślonego zachowania”?
  • Czy można NULLw ten sposób wykorzystywać ?
  • Czy jest coś złego w używaniu NULLjako wartości liczbowej w wyrażeniach arytmetycznych?
  • A jaki jest wynik i zachowanie w C ++ w tym przypadku?

Przeczytałem odpowiedzi na pytanie Jaka jest różnica między NULL, „\ 0” i 0 na temat różnicy między NULL, \0i 0jest, ale nie otrzymałem stamtąd zwięzłych informacji, jeśli jest to całkiem dopuszczalne, a także prawo do używania NULLjako wartość do obsługi w zadaniach i innych operacjach arytmetycznych.

RobertS obsługuje Monikę Cellio
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Samuel Liew
Naprawdę lepiej byłoby zadać dwa oddzielne pytania, jedno dla C i jedno dla C ++.
Konrad Rudolph

Odpowiedzi:

82

Czy mogę używać wskaźnika NULL jako zamiennika wartości 0?

Nie , nie jest to bezpieczne. NULLjest stałą wskaźnika zerowego, która może mieć typ int, ale bardziej typowo ma typ void *(w C), lub w inny sposób nie może być bezpośrednio przypisana do int(w C ++> = 11). Oba języki pozwalają na konwersję wskaźników na liczby całkowite, ale nie przewidują, że takie konwersje mogą być wykonywane w sposób dorozumiany (chociaż niektóre kompilatory zapewniają to jako rozszerzenie). Co więcej, chociaż konwersja wskaźnika zerowego na liczbę całkowitą w celu uzyskania wartości 0 jest powszechna, standard nie gwarantuje tego. Jeśli chcesz stałą z typem inti wartością 0, to przeliteruj ją 0.

  • Czy mogę przez to wkroczyć w Niezdefiniowane Zachowanie?

Tak, w każdej implementacji, w której NULLrozwija się do wartości typu void *lub innej, której nie można bezpośrednio przypisać int. Standard nie definiuje zachowania twojego zadania na takiej implementacji, ergo jego zachowanie jest niezdefiniowane.

  • czy dozwolone jest działanie z NULL w ten sposób?

Jest to kiepski styl, aw niektórych przypadkach może się zepsuć. O ile wydaje się, że używasz GCC, włamałoby się to na twój własny przykład, gdybyś skompilował z -Werroropcją.

  • Czy jest coś złego w używaniu NULL jako wartości liczbowej w wyrażeniach arytmetycznych?

Tak. Nie ma gwarancji, że w ogóle ma wartość liczbową. Jeśli masz na myśli 0, to napisz 0, które jest nie tylko dobrze zdefiniowane, ale także krótsze i wyraźniejsze.

  • A jaki jest wynik w C ++ w tym przypadku?

Język C ++ jest bardziej restrykcyjny w stosunku do konwersji niż C i ma inne reguły NULL, ale tam również implementacje mogą zapewniać rozszerzenia. Znów, jeśli masz na myśli 0, to właśnie powinieneś napisać.

John Bollinger
źródło
4
Należy zauważyć, że „który bardziej typowo ma typ void *” jest prawdziwe tylko dla C. void *nie jest legalnym typem dla C ++ (ponieważ nie można przypisać void*do żadnego innego typu wskaźnika). W C ++ 89 i C ++ 03 tak naprawdę NULL musi być typu int, ale w późniejszych wersjach może być (i zwykle jest) nullptr_t.
Martin Bonner wspiera Monikę
Mylisz się również, że konwersja void*na intzachowanie jest niezdefiniowana. Nie jest; jest to zachowanie określone w implementacji.
Martin Bonner wspiera Monikę
@MartinBonnersupportsMonica, W tych kontekstach, w których C określa, że ​​wskaźnik jest konwertowany na liczbę całkowitą, wynik konwersji jest rzeczywiście określony dla implementacji, ale nie o tym mówię. Jest to przypisanie wskaźnika do wartości typu liczb całkowitych (bez jawnej konwersji za pomocą rzutowania), która ma niezdefiniowane zachowanie. Język nie definiuje tam automatycznej konwersji.
John Bollinger,
@MartinBonnersupportsMonica, edytowałem, aby bardziej uwzględniać kwestie związane z C ++. W każdym razie centralny motyw dotyczy jednakowo obu języków: jeśli chcesz liczbę całkowitą 0, napisz ją jawnie jako stałą całkowitą odpowiedniego typu.
John Bollinger,
31

NULLto jakaś stała wskaźnika zerowego. W C może to być ciągłe wyrażenie całkowite o wartości 0lub takie wyrażenie rzucone na void*, przy czym to drugie jest bardziej prawdopodobne. Co oznacza, że nie możesz zakładać używania NULLzamiennie z zerem. Na przykład w tym kodzie przykładowym

char const* foo = "bar"; 
foo + 0;

Nie można zagwarantować, że zastąpienie 0go NULLjest poprawnym programem typu C, ponieważ dodawanie między dwoma wskaźnikami (nie mówiąc już o różnych typach wskaźników) nie jest zdefiniowane. Spowoduje to wydanie diagnostyki z powodu naruszenia ograniczenia. Argumenty dodawania nie będą ważne .


Jeśli chodzi o C ++, rzeczy są nieco inne. Brak niejawnej konwersji void*na inne typy obiektów oznaczał, że NULLhistorycznie był zdefiniowany jak 0w kodzie C ++. W C ++ 03 możesz prawdopodobnie uciec. Ale od C ++ 11 można go legalnie zdefiniować jako nullptrsłowo kluczowe . Teraz znowu pojawia się błąd, ponieważ std::nullptr_tnie można go dodać do typów wskaźników.

Jeśli NULLzostanie zdefiniowane jako, nullptrto nawet eksperyment stanie się nieważny. Nie ma konwersji z std::nullptr_tna liczbę całkowitą. Dlatego jest uważany za bezpieczniejszą stałą zerową wskaźnika.

StoryTeller - Unslander Monica
źródło
Dla kompletności 0L jest również stałą zerowego wskaźnika i może być używane jak NULLw obu językach.
eerorika
1
@jamesqf Standard mówi, że stała całkowita o wartości 0 jest stałą wskaźnika zerowego. Dlatego 0L jest stałą wskaźnika zerowego.
eerorika,
1
@eerorika: Po prostu to, czego potrzebuje świat, standardy ignorujące rzeczywistość :-) „Bo jeśli dobrze pamiętam mój zestaw 80286, nie możesz nawet przypisać dalekiego wskaźnika jako pojedynczej operacji, więc autorzy kompilatora musieliby specjalne - obuduj to.
jamesqf
2
@jamesqf Na C FAQ , na temat: ustawianie 0stałego wskaźnika zerowego: „najwyraźniej jako poprawka do całego istniejącego źle napisanego kodu C, który przyjmował błędne założenia”
Andrew Henle
3
@ jamesqf, że każda stała liczb całkowitych o wartości 0 jest stałą zerowego wskaźnika (w C) nie ma nic wspólnego z implementacjami wskaźnika sprzętowego. Należy również zauważyć, że standardowy C nie rozpoznaje rozróżnienia między wskaźnikami bliskimi i dalekimi, ale obsługuje przypisania wskaźnika do wskaźnika. Obsługuje także (niektóre) porównania wskaźników, które przedstawiają interesujące problemy dla segmentowanych formatów adresowania, takich jak 286.
John Bollinger,
21

Czy mogę używać wskaźnika NULL jako zamiennika wartości 0?

int i = NULL;

Zasady różnią się w zależności od języka i jego wersji. W niektórych przypadkach CAN oraz w innych, nie można. Niezależnie od tego nie powinieneś . Jeśli masz szczęście, kompilator ostrzeże, gdy spróbujesz, a nawet lepiej, kompilacja się nie powiedzie.

W C ++ przed C ++ 11 (cytat z C ++ 03):

[lib.support.types]

NULL jest zdefiniowaną implementacją stałą zerowego wskaźnika C ++ w tym standardzie międzynarodowym.

Nie ma sensu używać stałej zerowej wskaźnika jako liczby całkowitej. Jednak...

[conv.ptr]

Stała wskaźnika zerowego jest wartością całkowitą stałej wyrażenia (5.19) typu liczb całkowitych, której wartość wynosi zero.

Tak, technicznie by to działało, nawet jeśli byłoby bezsensowne. Z powodu tej techniki możesz napotkać źle napisane programy, które nadużywają NULL.

Od wersji C ++ 11 (cytat z najnowszej wersji roboczej):

[conv.ptr]

Stała NULL jest dosłownym całkowita ([lex.icon]) o wartości zerowej lub prvalue typu STD :: nullptr_t .

A std​::​nullptr_­tnie jest konwertowalne na liczbę całkowitą, więc użycie NULLjako liczby całkowitej działałoby tylko warunkowo, w zależności od wyborów dokonanych przez implementację języka.

PS nullptrjest typem wartości std​::​nullptr_­t. O ile nie potrzebujesz kompilacji programu w wersji wcześniejszej niż C ++ 11, powinieneś zawsze używać nullptrzamiast NULL.


C jest nieco inny (cytaty z projektu C11 N1548):

6.3.2.3 Język / Konwersje / Inne operandy / Wskaźniki

3 Wyrażenie stałej liczb całkowitych o wartości 0 lub takie wyrażenie rzutowane na typvoid * nazywane jest stałą wskaźnika zerowego. ...

Sprawa jest więc podobna do postu C ++ 11, tzn. Warunku nadużycia NULLprac w zależności od wyborów dokonanych przez implementację języka.

eerorika
źródło
10

Tak , ale w zależności od implementacji może być konieczna obsada. Ale tak, jest w 100% uzasadniony, w przeciwnym razie.

Chociaż jest to naprawdę, bardzo zły styl (nie trzeba dodawać?).

NULLjest lub faktycznie nie był C ++, to jest C. Standard ma jednak, podobnie jak w przypadku wielu wersji C, dwie klauzule ([diff.null] i [support.types.nullptr]), które technicznie tworzą NULLC ++. Jest to zdefiniowana przez implementację stała zerowego wskaźnika . Dlatego nawet jeśli jest to zły styl, technicznie jest tak C ++, jak to tylko możliwe.
Jak wskazano w przypisie , możliwe implementacje mogą być 0albo 0L, ale nie (void*)0 .

NULLmógłby oczywiście (standard nie mówi wprost, ale jest to właściwie jedyny wybór pozostały po 0lub 0L) nullptr. Prawie nigdy tak nie jest, ale jest to prawna możliwość.

Ostrzeżenie, które pokazał ci kompilator, pokazuje, że kompilator w rzeczywistości nie jest zgodny (chyba że skompilowałeś w trybie C). Ponieważ, no cóż, zgodnie z ostrzeżeniem, przekształcił wskaźnik zerowy (nie nullptrktóry z nich nullptr_t, który byłby wyraźny), więc najwyraźniej definicja NULLjest rzeczywiście (void*)0, a może nie być.

Tak czy inaczej, masz dwa możliwe uzasadnione (tj. Nie uszkodzony kompilator) przypadki. Albo (realistyczny przypadek), NULLjest czymś w rodzaju 0lub 0L, wtedy masz konwersję „zero lub jeden” na liczbę całkowitą i możesz zacząć.

Lub NULLrzeczywiście nullptr. W takim przypadku masz wyraźną wartość, która gwarantuje gwarancje porównania, a także jasno określone konwersje z liczb całkowitych, ale niestety nie na liczby całkowite. Ma jednak jasno określoną konwersję na bool(w wyniku false) i boolma jasno zdefiniowaną konwersję na liczbę całkowitą (w wyniku 0).

Niestety, to dwie konwersje, więc nie mieści się w „zero lub jeden”, jak wskazano w [konw.]. Tak więc, jeśli twoja implementacja zdefiniuje NULLjako nullptr, będziesz musiał dodać jawną rzutowanie, aby Twój kod był poprawny.

Damon
źródło
6

Z C faq:

P: Jeśli NULL i 0 są równoważne jako stałe wskaźnika zerowego, co powinienem użyć?

Odp .: Tylko w kontekstach wskaźnikowych są NULLi 0są równoważne. nieNULL powinien być używany, gdy wymagany jest inny rodzaj 0, nawet jeśli może on działać, ponieważ spowoduje to wysłanie niewłaściwego komunikatu stylistycznego. (Ponadto ANSI pozwala na zdefiniowanie wartości NULL , która w ogóle nie będzie działać w kontekstach innych niż wskaźnik). W szczególności nie należy jej używać, gdy pożądany jest znak zerowy ASCII (NUL). Podaj własną definicję((void *)0)NULL

http://c-faq.com/null/nullor0.html

phuclv
źródło
5

Oświadczenie: Nie znam C ++. Moja odpowiedź nie ma być stosowana w kontekście C ++

'\0'ma intwartość zero, tylko 100% dokładnie jak 0.

for (int k = 10; k > '\0'; k--) /* void */;
for (int k = 10; k > 0; k--) /* void */;

W kontekście wskaźników , 0i NULLw 100% równoważne:

if (ptr) /* ... */;
if (ptr != NULL) /* ... */;
if (ptr != '\0') /* ... */;
if (ptr != 0) /* ... */;

są w 100% równoważne.


Uwaga na temat ptr + NULL

Kontekst nieptr + NULL obejmuje wskazówek. Nie ma definicji dodawania wskaźników w języku C; wskaźniki i liczby całkowite można dodawać (lub odejmować). W przypadku, gdy jeden lub jest wskaźnikiem, drugi musi być liczbą całkowitą, więc jest skuteczny lub w zależności od definicji i kilku zachowań można się spodziewać: to wszystko działa, ostrzeżenie o konwersji między wskaźnikiem a liczbą całkowitą, brak kompilacji, .. .ptr + NULLptrNULLptr + NULL(int)ptr + NULLptr + (int)NULLptrNULL

pmg
źródło
Widziałem #define NULL (void *)0wcześniej Czy na pewno NULL i zwykły 0 są w 100% równoważne?
machine_1
2
W kontekście wskaźnika tak… warunek podkreślony w mojej odpowiedzi, dziękuję
pmg
@phuclv: Absolutnie nie mam pojęcia o C ++. Moja odpowiedź (poza bitem między nawiasami) dotyczy C
pmg
@ phuclv ptr + NULLnie jest używany NULLw kontekście wskaźników
pmg
3
@JesperJuhl: w kontekście wskaźników są one w 100% równoważne. Nie mam pojęcia, co nullptrjest, ale ((void*)0)i 0(lub '\0') są równoważne w kontekście wskaźników ...if (ptr == '\0' /* or equivalent 0, NULL */)
pmg
5

Nie, już nie wolał używać NULL(stary sposób inicjowania wskaźnika).

Od C ++ 11:

nullptrSłowo kluczowe oznacza literał wskaźnika. Jest to wartość typu std :: nullptr_t. Istnieją niejawne konwersje z nullptrna wartość zerowego wskaźnika dowolnego typu wskaźnika i dowolnego wskaźnika na typ elementu. Podobne konwersje istnieją dla dowolnej stałej wskaźnika zerowego, która zawiera wartości typu std::nullptr_toraz makro NULL.

https://en.cppreference.com/w/cpp/language/nullptr

Właściwie std :: nullptr_t jest typu null pointer dosłowne nullptr. Jest to odrębny typ, który sam nie jest typem wskaźnika ani wskaźnikiem typu elementu.

#include <cstddef>
#include <iostream>

void f(int* pi)
{
   std::cout << "Pointer to integer overload\n";
}

void f(double* pd)
{
   std::cout << "Pointer to double overload\n";
}

void f(std::nullptr_t nullp)
{
   std::cout << "null pointer overload\n";
}

int main()
{
    int* pi; double* pd;

    f(pi);
    f(pd);
    f(nullptr);  // would be ambiguous without void f(nullptr_t)
    // f(0);  // ambiguous call: all three functions are candidates
    // f(NULL); // ambiguous if NULL is an integral null pointer constant 
                // (as is the case in most implementations)
}

Wynik:

Pointer to integer overload
Pointer to double overload
null pointer overload
Mannoj
źródło
Pytanie dotyczy przypisania wartości NULL do 0 dla liczb całkowitych. W tym sensie nic nie zmienia się z nullptr zamiast NULL.
ivan.ukr
Użył słów jako „wskaźnika NULL”.
Mannoj,
Nawiasem mówiąc, C ++ nie ma koncepcji NULL po C ++ 11. Autor może być zdezorientowany przy użyciu constexpr lub zdefiniować inicjalizację starego sposobu. en.cppreference.com/w/cpp/language/default_initialization
Mannoj