Jak ta struktura może mieć sizeof == 0?

80

Jest stary post z prośbą o konstrukcję, dla której sizeofwróci 0. Istnieje kilka odpowiedzi z wysokimi wynikami od użytkowników o wysokiej reputacji, którzy twierdzą, że standardowo żaden typ ani zmienna nie może mieć rozmiaru 0. I zgadzam się z tym w 100%.

Jest jednak nowa odpowiedź, która przedstawia to rozwiązanie:

struct ZeroMemory {
    int *a[0];
};

Właśnie miałem zagłosować i skomentować to, ale czas spędzony tutaj nauczył mnie sprawdzać nawet rzeczy, co do których jestem w 100% pewien. Więc ... ku mojemu zdziwieniu zarówno gcci clangpokazują te same wyniki: sizeof(ZeroMemory) == 0. Co więcej, rozmiar zmiennej to 0:

ZeroMemory z{};
static_assert(sizeof(z) == 0); // Awkward...

Coaaa ...?

Łącze Godbolt

Jak to jest możliwe?

bolov
źródło
37
Tablica o zerowym rozmiarze nie jest standardowym C ++. ale rozszerzenie.
Jarod42
@ Jarod42 teraz to oczywiste.
bolov
1
Również nie kompiluje się pod MSVC
UnholySheep
7
Teraz umieść je w tablicy i wykonaj na nich arytmetykę wskaźników.
CodesInChaos
Hmmm ... Ile bajtów nic nie powinno zająć, jeśli nie 0?
Gerhardh

Odpowiedzi:

52

Zanim C został ujednolicony, wiele kompilatorów nie miałoby trudności z obsługą typów o zerowym rozmiarze, o ile kod nigdy nie próbowałby odjąć jednego wskaźnika do typu o zerowym rozmiarze od drugiego. Takie typy były pożyteczne, a ich wspieranie było łatwiejsze i tańsze niż zakazanie im. Inni kompilatorzy zdecydowali się jednak zabronić takich typów, a niektóre kody z asercjami statycznymi mogły opierać się na fakcie, że wyskakiwałyby, gdyby kod próbował utworzyć tablicę o rozmiarze zerowym. Autorzy normy stanęli przed wyborem:

  1. Zezwalaj kompilatorom na ciche akceptowanie deklaracji tablic o rozmiarze zerowym, nawet w przypadkach, gdy celem takich deklaracji byłoby wywołanie diagnostyki i przerwanie kompilacji, i wymaganie, aby wszystkie kompilatory akceptowały takie deklaracje (choć niekoniecznie po cichu) jako wytwarzające obiekty o rozmiarze zerowym .

  2. Zezwalaj kompilatorom na ciche akceptowanie deklaracji tablic o rozmiarze zerowym, nawet w przypadkach, gdy celem takich deklaracji byłoby wyzwolenie diagnostyki i przerwanie kompilacji, oraz zezwolenie kompilatorom napotykającym takie deklaracje na przerwanie kompilacji lub kontynuowanie jej w wolnym czasie.

  3. Wymagaj, aby implementacje wystawiały diagnostykę, jeśli kod deklaruje tablicę o rozmiarze zerowym, ale następnie zezwalaj implementacjom na przerwanie kompilacji lub kontynuowanie jej (z dowolną semantyką, którą uznają za odpowiednią) w wolnym czasie.

Autorzy Standardu zdecydowali się na # 3. W konsekwencji deklaracje tablic o rozmiarze zerowym są traktowane przez „rozszerzenie” standardu, mimo że takie konstrukcje były szeroko obsługiwane, zanim Standard ich zabronił.

Standard C ++ zezwala na istnienie pustych obiektów, ale w celu umożliwienia używania adresów pustych obiektów jako tokenów, nakazuje, aby miały one minimalny rozmiar 1. Dla obiektu, który nie ma elementów członkowskich, rozmiar 0 naruszałoby w ten sposób standard. Jeśli jednak obiekt zawiera elementy składowe o zerowej wielkości, C ++ Standard nie nakłada żadnych wymagań dotyczących sposobu przetwarzania poza faktem, że program zawierający taką deklarację musi wyzwolić diagnostykę. Ponieważ większość kodu używającego takich deklaracji oczekuje, że wynikowe obiekty będą miały rozmiar zerowy, najbardziej użytecznym zachowaniem dla kompilatorów odbierających taki kod jest traktowanie ich w ten sposób.

supercat
źródło
„Zanim C został ustandaryzowany”, masz na myśli zanim C99 istniał?
Stargateur
1
@Stargateur: Mam na myśli około 15 lat pomiędzy wynalezieniem C a napisaniem standardu C89. C99 dodał z powrotem ograniczoną formę obiektu o rozmiarze zerowym, aby uniknąć niektórych kludges niezbędnych do obejścia zakazu tablic o zerowym rozmiarze, ale takie kludges nie byłyby konieczne, gdyby C89 nie irytująco zabronił tablic o zerowym rozmiarze w pierwsze miejsce.
supercat
„Takie typy były przydatne” Jak były przydatne?
user109923,
1
@ user109923: Na przykład: struct polygon { int count; POINT sides[0];}; struct { struct polygon poly; POINT pts[3]; } myTriangle = { {3}, {{1,1},{2,2},{2,1} };. Należałoby zachować ostrożność, aby upewnić się, że wyrównanie nie powoduje problemów, ale można mieć obiekty o statycznym czasie trwania o różnych rozmiarach, które mogą być przetwarzane zamiennie.
supercat
42

Jak wskazał Jarod42, tablice o zerowej wielkości nie są standardowymi C ++, ale rozszerzeniami GCC i Clang.

Dodanie -pedanticpowoduje to ostrzeżenie:

5 : <source>:5:12: warning: zero size arrays are an extension [-Wzero-length-array]
    int *a[0];
           ^

Zawsze zapominam, że std=c++XX(zamiast std=gnu++XX) nie wyłącza wszystkich rozszerzeń.

To nadal nie wyjaśnia sizeofzachowania. Ale przynajmniej wiemy, że to nie jest standardowe ...

bolov
źródło
1
Tak czy inaczej, jest to zaskakujące, ponieważ nawet pusta struktura powinna dawać niezerowy rozmiar wartości ...
WF
2
Co ciekawe, jeśli utworzenie dwóch automatycznych przypadków, są one przechowywane sizeof(int*)osobno (zakładam): coliru.stacked-crooked.com/a/e9a3038bf587b65c
eerorika
9
To tylko odpowiada, że ​​tablice o zerowym rozmiarze są niestandardowe, ale nie wyjaśnia pytania, które zadałeś.
haccks
1
Zgodnie ze standardem zachowanie nie musi w żaden sposób mieć sensu - musi mieć sens tylko dla osób implementujących kompilator. Kompilator już pozwala na zdefiniowanie typu w sposób zabroniony przez standard. W związku z tym wymagania normy dotyczące tego, jaki wynik sizeofdaje dla tego typu, również nie mają zastosowania. Zachowanie jest zatem decyzją podjętą przez programistów kompilatora.
Peter
1
Pomysł jest ZeroMemory* z = (ZeroMemory*)malloc(sizeof(ZeroMemory) + 2 * sizeof(int*)); z.a[1] = new int{1}; /* or whatever, just use indices into a to access dynamic memory after the previous members (in this case: none) */taki, że oczywiście nie jest to zgodne ze standardem! (Może to być użyte do łatwego analizowania dynamicznej długości komunikatów sieciowych, na przykład przez rzutowanie do struktury).
hoffmale
19

W C ++ tablica o rozmiarze zerowym jest niedozwolona.

ISO / IEC 14882: 2003 8.3.4 / 1:

[..] Jeżeli obecne jest wyrażenie stałe (5.19), powinno to być całkowe wyrażenie stałe, a jego wartość musi być większa od zera . Wyrażenie stałe określa granicę (liczbę elementów) tablicy. Jeśli wartością wyrażenia stałego jest N, tablica ma Nelementy ponumerowane 0na N-1, a typ identyfikatora Dto „ tablica listy typów pochodnych deklaratora typuN T”. [..]

g ++ wymaga, aby -pedanticflaga ostrzegała o tablicy o rozmiarze zerowym.

msc
źródło
5

Tablice o zerowej długości są rozszerzeniem GCC i Clang. Zastosowanie sizeofdo tablic o zerowej długości daje zero .

Klasa C ++ (pusta) nie może mieć rozmiaru 0, ale pamiętaj, że klasa ZeroMemorynie jest pusta. Ma nazwany element członkowski o rozmiarze 0i zastosowanie sizeofzwróci zero.

haccks
źródło
5
więc ... pusta klasa nie może mieć rozmiaru 0, ale niepusta klasa może mieć rozmiar 0... to nie ma większego sensu. Ale wydaje mi się, że jest to rodzaj konfliktu, jaki można spotkać w przypadku niestandardowych rozszerzeń.
bolov
@bolov; Klasa c ++ (pusta) nie może mieć rozmiaru 0, zgodnie ze standardem pustej klasy. Standard C ++ mówi, że ponieważ nie ma innego sposobu na stworzenie struktury o rozmiarze 0. W tym konkretnym przypadku element struktury to self ma rozmiar 0, co powoduje, że rozmiar struktury 0. Wiem, że jest to zagmatwane, ale starałem się jak najlepiej wyjaśnić.
haccks
3
@bodov: Ale to nie jest klasa C ++, to klasa G ++, więc reguły C ++ nie muszą się do niej stosować. Zwróć szczególną uwagę, że nie możesz mieć tablicy tego typu ani wykonywać obliczeń matematycznych za pomocą jej wskaźników, więc zwykłe rozumowanie, że rozmiar nie może wynosić zero, również nie jest wystarczające.
Ben Voigt,