• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Przypisanie do wskaźnika wartości zero

VPS Starter Arubacloud
0 głosów
1,350 wizyt
pytanie zadane 12 marca 2017 w C i C++ przez Evelek Nałogowiec (28,960 p.)

Mamy taki oto wskaźnik:

int *wsk = 0;

0 powoduje "podobno" przypisanie do tego wskaźnika adresu 0, ze względu na bezpieczeństwo. Czy można tak robić i czy to jest poprawne? Czy od np. C++11 do takich rzeczy nie powinno się używać nullptr albo NULL?

3 odpowiedzi

+6 głosów
odpowiedź 12 marca 2017 przez mokrowski Mędrzec (156,220 p.)
wybrane 12 marca 2017 przez Evelek
 
Najlepsza

W języku C++ od wersji C++11, dostępny jest nullptr oraz nullptr_t który zapewnia poprawną inicjalizację wskaźnika. Standard języka C++ zapewnia że dealokacja takiego wskaźnika (czyli delete) zostanie wykonana poprawnie. To oznacza że możesz nie testować czy wskaźnik ma wartość nullptr bo testuje to delete i poprawnie usunie te dane (po prostu "cicho" nie zrobi nic...).To jest jeden z powodów dlaczego używamy nullptr. 

Inny powód to to że nullptr w zamierzony sposób pozbawiony jest możliwości konwersji na int co było problemem przy stosowaniu NULL. Typ nullptr_t może być typem argumentu w funkcji a taka funkcja zostanie wybrana dzięki mechanizmom polimorfizmu.

To co zrobiłeś w kodzie, oczywiście jest poprawne. Na platformach wbudowanych w ten sposób wskazuje się rejestry z danym numerem portu. Pracując w systemie operacyjnym, przypisując tu jakąś wartość, możesz jednak trafić na adresy danych które zostaną zniszczone lub na pamięć która nie została procesowi przydzielona. Wtedy program będzie zachowywał się w sposób nieprzewidziany.

Inną ciekawostką jest także to że nie na każdej platformie NULL będzie zerem.

komentarz 12 marca 2017 przez draghan VIP (106,230 p.)
Widzę kandydata do najki. :)
komentarz 13 marca 2017 przez Evelek Nałogowiec (28,960 p.)

Dziękuje mokrowski. Jak zawsze profesjonalna odpowiedź.

Po godzinie czytania i analizowania tego czuję, że załapuje powoli o co chodzi z tym nullptr. Tylko jakoś nie widzę potrzeby stosowania tego. Po co miałbym przypisać do wskaźnika wartość nullptr? Po co mi wskaźnik, który zajmuje tylko miejsce w pamięci i nic nie robi.

Też jako ciekawostkę wrzucę, bo przeczytałem tu o tym: http://www.geeksforgeeks.org/understanding-nullptr-c/ - "As a side note, nullptr is convertible to bool".

Napisałem coś takiego:

#include <iostream>
using namespace std;
int main()
{
	int *pi = nullptr;
	double *pd = 0;
	int *wskaznik = NULL;
	cout << pi << endl;
	cout << pd << endl;
	cout << wskaznik << endl;

	//cout << *pi << endl;
	//cout << *pd << endl;
	//cout << *wskaznik << endl;

	cout << &pi << endl;
	cout << &pd << endl;
	cout << &wskaznik << endl;
}

Spostrzeżenia:

  • Widać, że linie kodu objęte komentarzem się nie kompilują. Ma to sens w sumie.
  • Wyświetlając adres wskazywany przez wskaźniki, otrzymuje w każdym przypadku same zera.

 (Nie wspomnę o nullptr_t, które jest w ogóle dla mnie niezrozumiałe.... Jakoś nie potrafię tego ocenić, czy to jest bardziej podobne do typu zmiennej jak int, double itd. czy może chodzi o coś zupełnie innego).

komentarz 13 marca 2017 przez criss Mędrzec (172,590 p.)

Tylko jakoś nie widzę potrzeby stosowania tego. Po co miałbym przypisać do wskaźnika wartość nullptr? Po co mi wskaźnik, który zajmuje tylko miejsce w pamięci i nic nie robi.

nullptr możesz (i powinieneś) traktować jako umowną wartość przyjmowaną przez wskaźnik, kiedy chcesz powiedzieć, że nie wskazuje na nic konkretnego. Dlatego po dealokacji warto przypisać do wskaźnika wartość nullptr, żeby ewentualnie móc potem sprawdzić przyrównując wskaźnik do nullptr, albo po prostu wykorzystując konwersje na bool if(ptr) .

Nie wspomnę o nullptr_t, które jest w ogóle dla mnie niezrozumiałe...

nullptr_t to specjalny typ istniejący specjalnie dla nullptr (to jedyna wartość jaką można przypisać zmiennej tego typu). Każda konwersja na typ wskaźnikowy owocuje null pointer value (czyli z reguły 0). To czym nullptr_t właściwie jest jest pewnie zależne od implementacji. Przynajmniej na to wygląda, bo nigdzie nie doszukałem się takiej informacji. Sprecyzowane jest tylko co ma to robić. Ale w standardzie jest wyraźnie zaznaczone, że to jest odrębny typ, a nie jakiś typedef. Czyli na pewno nie powinieneś tego traktować jak jakikolwiek typ podstawowy. Draft standardu c++17, punkt 2.13.7

komentarz 13 marca 2017 przez mokrowski Mędrzec (156,220 p.)

Dobrze.. to może na przykładzie. Załóżmy że tworzysz funkcję która szuka w danym przedziale. Z jakiegoś powodu (tu przyjmij że "bo tak" czyli nie masz wyjścia), masz zwrócić wskaźnik na te dane. W komentarzu wpisane wątpliwości. Kod kompiluje się z ostrzeżeniem z powodu braku wykrycia zwracanej wartości.

#include <iostream>

const int * findFirstBetween(int lowBound, int highBound) {
    static const int values[] = { -2, -1, 0, 4, 6, 8, 11, 12, 33};

    if(lowBound >= highBound) {
        // TODO: Co zrobić jak zakres jest nieprawidłowy?
        // return Co?
    }
    for(const int * it = std::cbegin(values); it != std::cend(values); ++it) {
        if((*it >= lowBound) && (*it <= highBound)) {
            return it;
        }
    }
    // TODO: Co zrobić jak nie znajdzie?
    // retun Co?
}

int main() {
    // Jakiś prosty test....
    std::cout << *findFirstBetween(0, 3) << std::endl;
}

Proste. Zwrócę nullptr jako "sygnał" że albo nie znalazłem abo zakres jest nieprawidłowy. Takie stosowanie typu nazywane jest optional'em (przepraszam za anglicyzm). W przeciwnym wypadku powinienem zwracać jakąś wartość magiczną. Spójrz do funkcji w C. One często zwracają "magiczne" 0 czy -1 czy ... coś innego :-/ Najgorsze jest to że czasem nie ma wartości "wolnej" do takiej sygnalizacji. Taki wskaźnik pusty jest jednym z możliwych rozwiązań.

Poprawię kod i dodam wykrycie błędu.

#include <iostream>

const int * findFirstBetween(int lowBound, int highBound) {
    static const int values[] = {-2, -1, 0, 4, 6, 8, 11, 12, 33};
    const int * result = nullptr;

    if(lowBound >= highBound) {
        // Zakres nieprawidłowy
        return result;
    }
    for(const int * it = std::cbegin(values); it != std::cend(values); ++it) {
        if((*it >= lowBound) && (*it <= highBound)) {
            result = it;
        }
    }

    return result;
}

int main() {
    // Jakiś prosty test....
    const int * valuePtr = findFirstBetween(1, 5);
    if(valuePtr == nullptr) {
        std::cout << "Dane są niesdostępne lub zakres jest nieprawidłowy\n";
    } else {
        std::cout << "Wartość wynosi: " << *valuePtr << std::endl;
    }
}

Teraz zrozumiałe? Co do polimorfizmu z nullptr_t, także masz wątpliwości czy już wpadłeś dlaczego to dobre rozwiązanie? :-)

komentarz 13 marca 2017 przez Evelek Nałogowiec (28,960 p.)

Aż się uśmiecham sam do siebie jak przeanalizowałem oba programy, z nullptr oraz bez nullptr. Nawet mi w sumie nie przyszło do głowy nigdy, że można taki "myk" robić, kiedy funkcja ma zwrócić wskaźnik do "czegoś", ale nie ma do "czego" go zwrócić, a funkcja wymaga aby "coś" było zwrócone. I dzięki temu co teraz napisałeś, zrozumiałem też, po co ta gwiazdka przy funkcji: const int * findFirstBetween(int lowBound, int highBound). Jakoś wcześniej nie byłem tego pewny. Najlepiej zawsze na przykładach jak widać.

Pisałem wcześniej takie funkcje w języku C np. char *wczytaj(char *wyraz, int ile), które zwracały: return wyraz ale nie przywiązywałem uwagi do tego. Myślałem: "po prostu tak musi być, bo inaczej się nie kompiluje". To przy okazji tłumaczenia jednego zrozumiałem drugie. wink

Teraz jak przeczytałem wszystkie odpowiedzi do tego tematu to wszystko się zgadza z Twoim kodem. Czyli raz jeszcze - najlepiej zawsze na przykładach.

Co do nullptr_t to na internecie spotkałem się z przykładami jedynie przeciążania funkcji np.:

http://en.cppreference.com/w/cpp/types/nullptr_t

http://stackoverflow.com/questions/12066721/what-are-the-uses-of-the-type-stdnullptr-t

I wydaje mi się, że to zrozumiałem. Napisałem coś takiego:

//#include <cstddef>
#include <iostream>
using namespace std;

int *function(int *ptr_int, int integer) {
	cout << "Hello from int *function ";
	return ptr_int;
}

double *function(double *ptr_double, int integer) {
	cout << "Hello from double *function ";
	return ptr_double;
}

int *function(nullptr_t nullp, int integer) {
	cout << "Hello from int *function with nullptr_t ";
	static int compareValue = 5;
	if (compareValue > integer) {
		int *ptr = &compareValue;
		return ptr;
	}
	else
		return nullp;
}

int main()
{
	int a = 1;
	double b = 2.3;
	int *pi = &a;
	double *pd = &b;

	cout << function(pi, 3) << endl;
	cout << function(pd, 7.54) << endl;
	cout << function(nullptr, 7) << endl;

	for (;;);
}

I oczywiście dziękuje za poświęcony czas Tobie i Crissowi. Jak się czyta te Wasze komentarze teraz, to naprawdę wszystko się układa w całość. smiley

+2 głosów
odpowiedź 12 marca 2017 przez criss Mędrzec (172,590 p.)
Dlaczego "podobno"? Ewidentnie widać, że przypisujesz zero, więc ciężko oczekiwać, że wskaźnik nie zyska wartości 0.

Poprawne jest, ale bezpieczniej jest używać NULL, a jeszcze bezpieczniej nullptr, bo standard nie definiuje jakie wartości określa NULL czy nullptr. Co więcej - nullptr gwarantuje typ wskaźnikowy. To tak w teorii. W praktyce i tak NULL czy nullptr są zerami. Ale jak mówiłem - to tylko kwestia umowna, nie masz gwarancji, że to będzie 0. Poza tym kod jest czytelniejszy - gdy używasz NULL/nullptr od razu widać, że pracujesz na wskaźnikach.

Podsumowując: poprawne w zasadzie jest, tylko po co?
komentarz 12 marca 2017 przez Evelek Nałogowiec (28,960 p.)
  • Evelek: Czy można tak robić i czy to jest poprawne?
  • Criss: poprawne w zasadzie jest, tylko po co?

Pytasz po co - hm... zobaczyłem taki kod w pewnym miejscu i się zacząłem zastanawiać, bo nigdy tak nie robiłem. Zawsze przypisywałem adres do wskaźnika a nie cyfrę 0.

Dzięki Criss za wytłumaczenie. smiley

komentarz 13 marca 2017 przez criss Mędrzec (172,590 p.)
Adres, to liczba, więc bez znaczenia. Chodzi tylko o to, żeby to była właściwa liczba :P
0 głosów
odpowiedź 12 marca 2017 przez seba Dyskutant (8,900 p.)
Do wskaznika przypisuje sie adresy innych zmiennych.
komentarz 12 marca 2017 przez Evelek Nałogowiec (28,960 p.)
Wiem o tym i niekoniecznie zmiennych. Tu chodzi o ten konkretny przypadek.

Podobne pytania

0 głosów
1 odpowiedź 1,544 wizyt
pytanie zadane 28 października 2018 w C i C++ przez Wroteq98 Nowicjusz (200 p.)
+1 głos
1 odpowiedź 1,767 wizyt
pytanie zadane 9 maja 2021 w C i C++ przez Daaa22 Dyskutant (8,250 p.)

92,843 zapytań

141,782 odpowiedzi

320,858 komentarzy

62,174 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Wprowadzenie do ITsec, tom 2

Można już zamawiać tom 2 książki "Wprowadzenie do bezpieczeństwa IT" - będzie to około 650 stron wiedzy o ITsec (17 rozdziałów, 14 autorów, kolorowy druk).

Planowana premiera: 30.09.2024, zaś planowana wysyłka nastąpi w drugim tygodniu października 2024.

Warto preorderować, tym bardziej, iż mamy dla Was kod: pasja (użyjcie go w koszyku), dzięki któremu uzyskamy dodatkowe 15% zniżki! Dziękujemy zaprzyjaźnionej ekipie Sekuraka za kod dla naszej Społeczności!

...