Standard C gwarantuje, że size_t
jest to typ, który może przechowywać dowolny indeks tablicy. Oznacza to, że logicznie size_t
powinno być w stanie pomieścić dowolny typ wskaźnika. Czytałem na niektórych stronach, które znalazłem w Googles, że jest to legalne i / lub powinno zawsze działać:
void *v = malloc(10);
size_t s = (size_t) v;
Zatem w C99 standard wprowadził typy intptr_t
i uintptr_t
, które są podpisane, a typy niepodpisane gwarantują, że będą mogły przechowywać wskaźniki:
uintptr_t p = (size_t) v;
Jaka jest różnica między używaniem size_t
a uintptr_t
? Oba są niepodpisane i oba powinny być w stanie pomieścić dowolny typ wskaźnika, więc wydają się funkcjonalnie identyczne. Czy istnieje jakiś naprawdę ważny powód, aby używać uintptr_t
(lub jeszcze lepiej: a void *
) zamiast a size_t
, innego niż jasność? Czy w nieprzejrzystej strukturze, w której pole będzie obsługiwane tylko przez funkcje wewnętrzne, czy jest jakiś powód, aby tego nie robić?
Z tego samego powodu, ptrdiff_t
czy był typem podpisanym zdolnym do utrzymywania różnic kursorów, a zatem zdolnym do utrzymywania większości wskaźników, więc czym się różni intptr_t
?
Czy wszystkie te typy nie służą w zasadzie trywialnie różnym wersjom tej samej funkcji? Jeśli nie to dlaczego? Co nie mogę zrobić z jednym z nich, czego nie mogę zrobić z innym? Jeśli tak, to dlaczego C99 dodał dwa zasadniczo zbędne typy do języka?
Jestem gotów zignorować wskaźniki funkcji, ponieważ nie dotyczą one obecnego problemu, ale śmiało mogę o nich wspomnieć, ponieważ mam podejrzenia, że będą kluczowe dla „poprawnej” odpowiedzi.
size_t
auintptr_t
ale coptrdiff_t
iintptr_t
- nie oba z nich będzie w stanie przechowywać ten sam zakres wartości niemal na każdej platformie? Dlaczego mamy zarówno podpisane, jak i niepodpisane typy liczb całkowitych wielkości wskaźnika, szczególnie jeśliptrdiff_t
już służą celowi podpisanego typu liczby całkowitej.size_t
co najmniej 16 bitów, aleptrdiff_t
co najmniej 17 bitów (co w praktyce oznacza, że prawdopodobnie będzie to co najmniej 32 bity).SIZE_MAX
że nie powinien wynosić 2 ** 64. Pamiętaj, że jest to adresowanie płaskie. segmentacja nie jest konieczna, aby uzyskać niedopasowanie międzySIZE_MAX
wskaźnikiem danych a zakresem.Jeśli chodzi o twoje oświadczenie:
Jest to w rzeczywistości błąd (nieporozumienie wynikające z niewłaściwego rozumowania) (a) . Możesz myśleć, że to drugie wynika z pierwszego, ale tak nie jest.
Wskaźniki i indeksy tablicowe to nie to samo. Można sobie wyobrazić zgodną implementację, która ogranicza tablice do 65536 elementów, ale pozwala wskaźnikom na adresowanie dowolnej wartości w ogromnej 128-bitowej przestrzeni adresowej.
C99 stwierdza, że górna granica
size_t
zmiennej jest zdefiniowana przezSIZE_MAX
i może wynosić nawet 65535 (patrz C99 TR3, 7.18.3, niezmieniony w C11). Wskaźniki byłyby dość ograniczone, gdyby były ograniczone do tego zakresu w nowoczesnych systemach.W praktyce prawdopodobnie okaże się, że twoje założenie się utrzymuje, ale nie dlatego, że standard to gwarantuje. Ponieważ tak naprawdę to nie gwarantuje.
(a) Nawiasem mówiąc, nie jest to jakaś forma osobistego ataku, tylko stwierdzenie, dlaczego twoje wypowiedzi są błędne w kontekście krytycznego myślenia. Na przykład następujące rozumowanie jest również nieprawidłowe:
Bystry lub inny charakter szczeniąt nie ma tutaj znaczenia, wszystko, co stwierdzam, to fakt, że dwa fakty nie prowadzą do wniosku, ponieważ dwa pierwsze zdania pozwalają na istnienie uroczych rzeczy, które nie są szczeniętami.
Jest to podobne do pierwszego stwierdzenia, które niekoniecznie nakazuje drugie.
źródło
ptrdiff_t
vs.intptr_t
).Pozwolę, aby wszystkie pozostałe odpowiedzi były uzasadnione ograniczeniami segmentów, egzotycznymi architekturami i tak dalej.
Czy prosta różnica w nazwach nie jest wystarczającym powodem, aby użyć właściwego typu dla właściwej rzeczy?
Jeśli przechowujesz rozmiar, użyj
size_t
. Jeśli przechowujesz wskaźnik, użyjintptr_t
. Osoba czytająca Twój kod od razu wie, że „aha, to jest rozmiar czegoś, prawdopodobnie w bajtach”, i „och, z jakiegoś powodu tutaj jest przechowywana wartość wskaźnika jako liczba całkowita”.W przeciwnym razie możesz po prostu użyć
unsigned long
(lub, w dzisiejszych czasach,unsigned long long
) wszystkiego. Rozmiar to nie wszystko, nazwy typów mają znaczenie, które jest przydatne, ponieważ pomaga opisać program.źródło
size_t
polu.void*
,intptr_t
iuintptr_t
są gwarantowane, aby móc reprezentować dowolny wskaźnik do danych.Możliwe, że rozmiar największej tablicy jest mniejszy niż wskaźnik. Pomyśl o architekturach podzielonych na segmenty - wskaźniki mogą być 32-bitowe, ale pojedynczy segment może być w stanie adresować tylko 64 KB (na przykład stara architektura 8086 w trybie rzeczywistym).
Chociaż nie są one już powszechnie używane w komputerach stacjonarnych, standard C ma obsługiwać nawet małe, wyspecjalizowane architektury. Nadal rozwijane są systemy na przykład z 8 lub 16 bitowymi procesorami.
źródło
size_t
też powinieneś sobie z tym poradzić? A może tablice dynamiczne w jakimś odległym segmencie nadal byłyby ograniczone do indeksowania w obrębie tego segmentu?str
funkcji Borland nawet dlamem
funkcji (memset
,memcpy
,memmove
). Oznaczało to, że mogłeś zastąpić część pamięci, gdy przesunęło się przesunięcie, fajnie było debugować na naszej wbudowanej platformie.Wyobrażam sobie (i dotyczy to wszystkich nazw typów), że lepiej oddaje twoje intencje w kodzie.
Na przykład, mimo że
unsigned short
iwchar_t
są tego samego rozmiaru w systemie Windows (myślę), użyciewchar_t
zamiastunsigned short
pokazuje zamiar użycia go do przechowywania szerokiego znaku, a nie tylko dowolnej liczby.źródło
wchar_t
jest znacznie większa niżunsigned short
tak, więc użycie jednego do drugiego byłoby błędne i spowodowałoby poważny (i nowoczesny) problem z przenośnością, podczas gdy problemy z przenośnością międzysize_t
iuintptr_t
wydają się leżeć w odległych krajach z 1980-coś (losowe dźgnięcie w ciemność na randkę, tam)size_t
iuintptr_t
nadal sugerują użycie w swoich nazwach.Patrząc zarówno do tyłu, jak i do przodu, i przypominając sobie, że różne architektury dziwaków były rozproszone po krajobrazie, jestem prawie pewien, że próbowali zawinąć wszystkie istniejące systemy, a także zapewnić wszystkie możliwe przyszłe systemy.
Tak więc, w sposób ustalony, do tej pory nie potrzebowaliśmy tak wielu typów.
Ale nawet w LP64, dość powszechnym paradygmacie, potrzebowaliśmy size_t i ssize_t dla interfejsu wywołania systemowego. Można sobie wyobrazić bardziej ograniczony system przyszłości lub przyszłości, w którym użycie pełnego 64-bitowego typu jest kosztowne i mogą chcieć korzystać z operacji we / wy większych niż 4 GB, ale nadal mają wskaźniki 64-bitowe.
Myślę, że musisz się zastanawiać: co mogło zostać opracowane, co może przyjść w przyszłości. (Być może 128-bitowe wskaźniki dla całego systemu rozproszonego w Internecie, ale nie więcej niż 64 bity w wywołaniu systemowym, a może nawet „32-bitowy” starszy limit :-) Obraz, że starsze systemy mogą uzyskać nowe kompilatory C. .
Zobacz także, co wtedy istniało. Oprócz modeli pamięci w prawdziwym trybie zillion 286, co powiesz na 60-bitowe mainframe słowa / 18-bitowego CDC? A co z serią Cray? Nie wspominając o normalnym ILP64, LP64, LLP64. (Zawsze myślałem, że Microsoft był pretensjonalny z LLP64, powinien to być P64). Z pewnością mogę sobie wyobrazić komitet próbujący objąć wszystkie bazy ...
źródło
Oznacza to, że intptr_t musi zawsze zastępować size_t i visa versa.
źródło