Standard C11 mówi, że tablice, zarówno o wielkości, jak i o zmiennej długości „powinny mieć wartość większą niż zero”. Jakie jest uzasadnienie niedopuszczenia długości 0?
Zwłaszcza w przypadku tablic o zmiennej długości doskonale jest mieć rozmiar zero co jakiś czas. Jest także przydatny w przypadku tablic statycznych, gdy ich rozmiar pochodzi z opcji konfiguracji makra lub kompilacji.
Co ciekawe, GCC (i clang) zapewniają rozszerzenia, które umożliwiają tablice o zerowej długości. Java pozwala także na tablice o długości zero.
struct { int p[1],q[1]; } foo; int *pp = p+1;
,pp
byłby prawidłowym wskaźnikiem, ale*pp
nie miałby unikalnego adresu. Dlaczego ta sama logika nie mogła się utrzymać w przypadku tablicy o zerowej długości? Powiedzieć, że biorąc pod uwagęint q[0];
w ramach struktury ,q
by odnosić się do adresu, którego ważność będzie podobnie jak wp+1
powyższym przykładzie.Odpowiedzi:
Problemem, który postawiłbym, jest to, że tablice C są tylko wskaźnikami do początku przydzielonej części pamięci. Posiadanie rozmiaru 0 oznaczałoby, że masz wskaźnik do ... nic? Nie możesz mieć nic, więc musiałaby zostać wybrana dowolna rzecz. Nie możesz użyć
null
, ponieważ wtedy tablice o długości 0 wyglądałyby jak wskaźniki zerowe. I w tym momencie każda inna implementacja wybierze różne arbitralne zachowania, prowadzące do chaosu.źródło
sizeof
0 i jak to spowodowałoby kłopoty. Wszystko to można wyjaśnić przy użyciu odpowiednich pojęć i terminologii bez utraty zwięzłości lub jasności. Pomieszanie tablic i wskaźników ryzykuje jedynie rozprzestrzenieniem tablic = nieporozumienie wskaźników (co jest ważniejsze w innych kontekstach) bez korzyści.Spójrzmy, jak tablica jest zwykle układana w pamięci:
Zauważ, że nie ma oddzielnego obiektu o nazwie,
arr
który przechowuje adres pierwszego elementu; gdy w wyrażeniu pojawia się tablica, C oblicza adres pierwszego elementu w razie potrzeby.Więc pomyślmy o tym: tablica 0-element miałby żadnego przechowywania Odstawić na nim, czyli nie ma nic do obliczania adresu tablicy z (innymi słowy, nie ma odwzorowania obiektu dla identyfikatora). To tak, jakby powiedzieć: „Chcę utworzyć
int
zmienną, która nie zajmuje pamięci”. To nonsensowna operacja.Edytować
Tablice Java to zupełnie inne zwierzęta niż tablice C i C ++; nie są typem prymitywnym, ale typem pochodnym
Object
.Edytuj 2
Punkt poruszony w poniższych komentarzach - ograniczenie „większe niż 0” dotyczy tylko tablic, w których rozmiar jest określony przez wyrażenie stałe ;
VLA może mieć długość 0Deklaracja VLA z nietrwałym wyrażeniem o wartości 0 nie jest naruszeniem ograniczenia, ale wywołuje niezdefiniowane zachowanie.Oczywiste jest, że VLA to inne zwierzęta niż zwykłe tablice
, a ich implementacja może pozwolić na rozmiar 0. Nie można ich zadeklarowaćstatic
ani w zakresie plików, ponieważ rozmiar takich obiektów musi być znany przed uruchomieniem programu.Nie jest również nic warte, że od C11 implementacje nie są wymagane do obsługi VLA.
źródło
T a[0]
jakoT *a
, ale dlaczego nie po prostu użyćT *a
?struct
Hack. Nigdy nie użyłem tego osobiście; nigdy nie pracował nad problemem, który wymagałby zmiennej wielkościstruct
.Zazwyczaj chciałbyś, aby tablica rozmiarów zero (w rzeczywistości zmiennych) znała swój rozmiar w czasie wykonywania. Następnie spakuj to
struct
i użyj elastycznych elementów tablicy , takich jak np .:Oczywiście elastyczny element tablicy musi być ostatnim w swoim rodzaju
struct
i musisz mieć coś wcześniej. Często byłoby to coś związanego z faktyczną długością zajmowanego przez środowisko wykonawcze tego elastycznego elementu tablicy.Oczywiście przydzieliłbyś:
AFAIK, było to już możliwe w C99 i jest bardzo przydatne.
BTW, elastyczne elementy tablicy nie istnieją w C ++ (ponieważ trudno byłoby określić, kiedy i jak należy je zbudować i zniszczyć). Zobacz jednak przyszłość std :: dynarray
źródło
Jeśli wyrażenie
type name[count]
jest zapisane w jakiejś funkcji, to powiesz kompilatorowi C, aby przydzieliłsizeof(type)*count
bajty ramki stosu i obliczył adres pierwszego elementu w tablicy.Jeśli wyrażenie
type name[count]
jest zapisane poza wszystkimi definicjami funkcji i struktur, należy poinformować kompilator C, aby przydzieliłsizeof(type)*count
bajty segmentu danych i obliczył adres pierwszego elementu w tablicy.name
w rzeczywistości jest stałym obiektem, który przechowuje adres pierwszego elementu w tablicy, a każdy obiekt, który przechowuje adres pewnej pamięci, jest nazywany wskaźnikiem, dlatego jest to powód, dla którego traktujesz goname
jako wskaźnik, a nie tablicę. Należy pamiętać, że do tablic w C można uzyskać dostęp tylko poprzez wskaźniki.Jeśli
count
jest stałym wyrażeniem, którego wynikiem jest zero, to powiesz kompilatorowi C, aby przydzielił zero bajtów albo ramce stosu, albo segmentowi danych i zwrócił adres pierwszego elementu w tablicy, ale problem polega na tym, że pierwszy element tablicy zerowej długości nie istnieje i nie można obliczyć adresu czegoś, co nie istnieje.Jest to racjonalne, że element nr.
count+1
nie istnieje wcount
tablicy -długości, dlatego kompilator C zabrania definiowania tablicy o zerowej długości jako zmiennej wewnątrz i na zewnątrz funkcji, ponieważ jaka jest wówczas jej zawartośćname
? Jakiname
dokładnie adres przechowuje?Jeśli
p
jest wskaźnikiem, to wyrażeniep[n]
jest równoważne z*(p + n)
Tam, gdzie gwiazdka * w odpowiednim wyrażeniu oznacza dereferencję operacji wskaźnika, co oznacza dostęp do pamięci wskazanej przez
p + n
lub dostęp do pamięci, w której adres jest zapisanyp + n
, gdziep + n
wyrażenie wyrażenia, bierze adresp
i dodaje do tego adresu liczbęn
pomnożoną przez rozmiar typu wskaźnikap
.Czy można dodać adres i numer?
Tak, jest to możliwe, ponieważ adres jest liczbą całkowitą bez znaku powszechnie reprezentowaną w notacji szesnastkowej.
źródło
N
maN+1
skojarzone adresy,N
z których pierwszy identyfikuje unikatowe bajty, a ostatniN
z każdego punktu tuż za jednym z tych bajtów. Taka definicja działałaby dobrze nawet w zdegenerowanym przypadku, w którymN
wynosi 0.Jeśli chcesz wskaźnik do adresu pamięci, zadeklaruj go. Tablica faktycznie wskazuje na zarezerwowaną część pamięci. Tablice zanikają do wskaźników po przekazaniu do funkcji, ale jeśli pamięć, na którą wskazują, znajduje się na stosie, nie ma problemu. Nie ma powodu, aby deklarować tablicę o rozmiarze zero.
źródło
Od czasów oryginalnej wersji C89, kiedy norma C określała, że coś ma niezdefiniowane zachowanie, oznaczało to: „Rób wszystko, co sprawi, że implementacja na konkretnej platformie docelowej będzie najbardziej odpowiednia do zamierzonego celu”. Autorzy Standardu nie chcieli zgadywać, jakie zachowania mogą być najbardziej odpowiednie do określonego celu. Istniejące implementacje C89 z rozszerzeniami VLA mogły mieć inne, ale logiczne zachowania, gdy otrzymały rozmiar zero (np. Niektóre mogły traktować tablicę jako wyrażenie adresowe dające wartość NULL, podczas gdy inne traktują ją jako wyrażenie adresowe, które może być równe adresowi inna dowolna zmienna, ale można bezpiecznie dodać zero bez pułapki). Gdyby jakikolwiek kod mógł polegać na tak różnych zachowaniach, autorzy Standardu nie „
Zamiast próbować odgadnąć, co mogą zrobić implementacje, lub sugerować, że każde zachowanie powinno być uważane za lepsze od innych, autorzy Standardu po prostu zezwolili implementatorom na użycie osądu przy rozpatrywaniu tej sprawy w sposób, który uznają za najlepszy. Implementacje, które wykorzystują malloc () za kulisami, mogą traktować adres tablicy jako NULL (jeśli malloc o rozmiarze zero daje zero), te, które używają obliczeń adresu stosu mogą dać wskaźnik, który pasuje do adresu innej zmiennej, a niektóre inne implementacje mogą to zrobić inne rzeczy. Nie sądzę, że spodziewali się, że autorzy kompilatorów zrobią wszystko, aby skrzynka narożna o zerowym rozmiarze zachowywała się celowo bezużyteczna.
źródło