AFAIK, chociaż nie możemy stworzyć macierzy pamięci statycznej o rozmiarze 0, ale możemy to zrobić za pomocą dynamicznych:
int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined
Jak czytałem, p
działa jak element jednego końca. Mogę wydrukować adres, który p
wskazuje.
if(p)
cout << p << endl;
Chociaż jestem pewien, że nie możemy wyłapać tego wskaźnika (element ostatni-ostatni), tak jak nie możemy z iteratorami (element ostatni-ostatni), ale nie jestem pewien, czy zwiększać ten wskaźnik
p
? Czy zachowanie niezdefiniowane (UB) jest podobne do iteratorów?p++; // UB?
c++
pointers
undefined-behavior
dynamic-arrays
Itachi Uchiwa
źródło
źródło
std::vector
z 0 pozycjami.begin()
jest już równy,end()
więc nie można zwiększyć iteratora, który wskazuje na początek.Odpowiedzi:
Wskaźniki do elementów tablic mogą wskazywać na prawidłowy element lub jeden za końcem. Jeśli zwiększysz wskaźnik w sposób, który przekracza więcej niż jeden koniec, zachowanie jest niezdefiniowane.
W przypadku tablicy o rozmiarze 0
p
wskazuje ona już jeden koniec, więc zwiększanie jej nie jest dozwolone.Patrz C ++ 17 8.7 / 4 dotyczący
+
operatora (++
ma takie same ograniczenia):źródło
x[i]
jest takie samo jakx[i + j]
to, gdy obai
ij
mają wartość 0?x[i]
to ten sam element,x[i+j]
jakbyj==0
.Myślę, że masz już odpowiedź; Jeśli spojrzysz nieco głębiej: Powiedziałeś, że zwiększenie iteratora off-the-end to UB, więc: Ta odpowiedź brzmi: co to jest iterator?
Iterator jest tylko obiektem, który ma wskaźnik i zwiększa, że iterator tak naprawdę zwiększa wskaźnik, który ma. Tak więc w wielu aspektach iterator jest traktowany jako wskaźnik.
Pochodzi z C ++ primer 5 edycja firmy Lipmann.
Więc to UB, nie rób tego.
źródło
W najściślejszym sensie nie jest to zachowanie niezdefiniowane, ale zdefiniowane w implementacji. Tak więc, choć niewskazane jest, jeśli planujesz wspierać architekturę spoza głównego nurtu, prawdopodobnie możesz to zrobić.
Standardowy cytat podany przez interjay jest dobry, wskazując UB, ale moim zdaniem jest to drugi najlepszy hit, ponieważ dotyczy arytmetyki wskaźnik-wskaźnik (zabawnie, jeden jest jawnie UB, a drugi nie). W pytaniu znajduje się akapit dotyczący operacji bezpośrednio:
Och, poczekaj chwilę, całkowicie zdefiniowany typ obiektu? To wszystko? To znaczy, tak naprawdę, wpisać ? Więc w ogóle nie potrzebujesz przedmiotu?
Potrzebne jest sporo czytania, aby znaleźć wskazówkę, że coś w tym miejscu może nie być tak dobrze zdefiniowane. Ponieważ do tej pory brzmi to tak, jakbyś mógł to zrobić bez żadnych ograniczeń.
[basic.compound] 3
wypowiada się o tym, jaki typ wskaźnika można mieć, a ponieważ nie ma żadnej z pozostałych trzech, wynik twojej operacji wyraźnie mieści się w 3.4: nieprawidłowy wskaźnik .Nie oznacza to jednak, że nie możesz mieć nieprawidłowego wskaźnika. Przeciwnie, wymienia niektóre bardzo powszechne, normalne warunki (np. Koniec okresu przechowywania), w których wskaźniki regularnie stają się nieważne. Tak więc najwyraźniej jest to dozwolone. I rzeczywiście:
Robimy tam „każdy inny”, więc nie jest to zachowanie nieokreślone, ale zdefiniowane w ramach implementacji, dlatego ogólnie dopuszczalne (chyba że implementacja wyraźnie mówi coś innego).
Niestety, to nie koniec historii. Chociaż wynik netto nie zmienia się odtąd, staje się coraz bardziej mylący, im dłużej będziesz szukać „wskaźnika”:
Czytaj jako: OK, kogo to obchodzi! Tak długo, jak wskazuje wskaźnik gdzieś w pamięci , jestem dobry?
Odczytaj jako: OK, bezpiecznie uzyskany, cokolwiek. Nie wyjaśnia, co to jest, ani nie mówi, że faktycznie go potrzebuję. Bezpiecznie wyprowadzony z cholery. Najwyraźniej nadal mogę mieć niezabezpieczone wskaźniki. Domyślam się, że dereferencjowanie ich prawdopodobnie nie byłoby tak dobrym pomysłem, ale ich posiadanie jest całkowicie dozwolone. Nie mówi inaczej.
Och, więc to może nie mieć znaczenia, tak jak myślałem. Ale czekaj ... „może nie”? Oznacza to, że może również . Skąd mam wiedzieć?
Czekaj, więc to możliwe, że muszę wywoływać
declare_reachable()
każdy wskaźnik? Skąd mam wiedzieć?Teraz możesz przekonwertować na
intptr_t
, który jest dobrze zdefiniowany, dając całkowitą reprezentację bezpiecznie uzyskanego wskaźnika. Dla których oczywiście, jako liczba całkowita, jest całkowicie uzasadnione i dobrze zdefiniowane, aby zwiększać ją według własnego uznania.I tak, możesz przekonwertować
intptr_t
powrót na wskaźnik, który jest również dobrze zdefiniowany. Po prostu, nie będąc oryginalną wartością, nie ma już gwarancji, że masz bezpiecznie wyprowadzony wskaźnik (oczywiście). Mimo wszystko, zgodnie z literą standardu, podczas gdy jest definiowany jako implementacja, jest to w 100% uzasadniona czynność:Haczyk
Wskaźniki są zwykłymi liczbami całkowitymi, tylko ty używasz ich jako wskaźników. Och, gdyby to tylko prawda!
Niestety, istnieją architektury, w których to wcale nie jest prawdą, a samo wygenerowanie niepoprawnego wskaźnika (nie dereferencjowanie go, po prostu umieszczenie go w rejestrze wskaźnika) spowoduje pułapkę.
To jest podstawa „zdefiniowanej implementacji”. To oraz fakt, że zwiększanie wskaźnika w dowolnym momencie, jak możesz, może oczywiście spowodować przepełnienie, z którym standard nie chce sobie poradzić. Koniec przestrzeni adresowej aplikacji może nie pokrywać się z lokalizacją przepełnienia, a nawet nie wiadomo, czy istnieje coś takiego jak przepełnienie wskaźników dla określonej architektury. Podsumowując, jest to koszmarny bałagan, bez związku z możliwymi korzyściami.
Z drugiej strony radzenie sobie z warunkiem jednego obiektu przeszłości jest łatwe: implementacja musi po prostu upewnić się, że żaden obiekt nie jest nigdy przydzielony, aby zajęty został ostatni bajt w przestrzeni adresowej. Jest to dobrze zdefiniowane, ponieważ jest użyteczne i trywialne do zagwarantowania.
źródło
+
operatora (z którego++
wypływa), co oznacza, że wskazywanie po „jeden po zakończeniu” jest niezdefiniowane.