Як ця структура може мати sizeof == 0?


80

Існує стара публікація з проханням про конструкцію, для якої sizeofслід повернутися 0. Є декілька високих балів відповідей від користувачів з високою репутацією, які заявляють, що за стандартом жоден тип або змінна не може мати розмір 0. І я на 100% з цим погоджуюсь.

Однак є нова відповідь, яка представляє таке рішення:

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

Я просто збирався проголосувати і прокоментувати це, але час, проведений тут, навчив мене перевіряти навіть те, у чому я впевнений на 100%. Так ... на мій подив , як gccі clangпоказують ті ж результати: sizeof(ZeroMemory) == 0. Навіть більше, розмір змінної 0:

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

Whaaaat ...?

Годболт ланка

Як це можливо?


37
Масив нульового розміру не є стандартним для C ++. але розширення.
Jarod42,

@ Jarod42 тепер це очевидно.
болов

1
Крім того, він не компілюється під MSVC
UnholySheep

7
Тепер помістіть їх у масив і зробіть на них арифметику покажчика.
CodesInChaos

Мда ... Скільки байтів нічого не повинно займати, якщо не 0?
Герхард

Відповіді:


52

До того, як C був стандартизований, багато компіляторів не мали б труднощів з обробкою типів нульового розміру, поки код ніколи не намагався відняти один вказівник на тип нульового розміру від іншого. Такі типи були корисними, і підтримувати їх було простіше і дешевше, ніж забороняти. Однак інші компілятори вирішили заборонити подібні типи, і деякі коди статичного твердження, можливо, покладались на той факт, що вони будуть кричати, якби код намагався створити масив нульового розміру. Автори «Стандарту» постали перед вибором:

  1. Дозволити компіляторам мовчки приймати декларації масиву нульового розміру, навіть у тих випадках, коли метою таких декларацій буде ініціювати діагностику та перервати компіляцію, і вимагати, щоб усі компілятори приймали такі декларації (хоча і не обов'язково мовчки) як об'єкти нульового розміру. .

  2. Дозвольте компіляторам мовчки приймати декларації масиву нульового розміру, навіть у тих випадках, коли метою таких декларацій буде ініціювати діагностику та перервати компіляцію, і дозволити компіляторам, які стикаються з такими деклараціями, або скасувати компіляцію, або продовжити її на дозвіллі.

  3. Вимагайте, щоб реалізації видавали діагностику, якщо код оголошує масив нульового розміру, але потім дозволити реалізаціям або перервати компіляцію, або продовжити її (з будь-якою семантикою, яку вони вважають за потрібне) на дозвіллі.

Автори «Стандарту» обрали № 3. Отже, декларації масиву нульового розміру розглядаються Стандартним "розширенням", навіть незважаючи на те, що такі конструкції широко підтримувались до того, як Стандарт забороняв їх.

Стандарт C ++ допускає існування порожніх об'єктів, але, намагаючись дозволити адреси порожніх об'єктів використовуватись як маркери, він передбачає мінімальний розмір 1. Для об'єкта, який не має членів, розмір 0, таким чином, порушить Стандарт. Однак, якщо об'єкт містить члени нульового розміру, стандарт C ++ не встановлює жодних вимог щодо того, як він обробляється, окрім того, що програма, що містить таке оголошення, повинна запускати діагностику. Оскільки більшість коду, який використовує такі оголошення, очікує, що отримані об'єкти матимуть нульовий розмір, найкориснішою поведінкою компіляторів, які отримують такий код, є поводження з ними таким чином.


"До того, як C був стандартизований", ви маєте на увазі до того, як C99 існував?
Stargateur

1
@Stargateur: Я маю на увазі приблизно 15 років між тим, коли був винайдений C і коли був написаний стандарт C89. C99 додав назад обмежену форму об'єкта нульового розміру, щоб уникнути деяких клубів, необхідних, щоб обійти заборону масивів нульового розміру, але такі клуди не були б необхідними, якби C89 не дратував заборонені масиви нульового розміру в перше місце.
supercat

"Такі типи були корисні" Чим вони були корисні?
user109923

1
@ User109923: В якості прикладу: struct polygon { int count; POINT sides[0];}; struct { struct polygon poly; POINT pts[3]; } myTriangle = { {3}, {{1,1},{2,2},{2,1} };. Потрібно бути обережним, щоб вирівнювання не спричиняло проблем, але можна було б мати об’єкти статичної тривалості різного розміру, які можна обробляти як взаємозамінні.
Supercat

42

Як зазначає Jarod42, масиви нульового розміру не є стандартними C ++, а розширеннями GCC та Clang.

Додавання -pedanticвидає таке попередження:

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

Я завжди забуваю, що std=c++XX(замість std=gnu++XX) не відключає всі розширення.

Це все ще не пояснює sizeofповедінку. Але принаймні ми знаємо, що це не стандартно ...


1
У будь-якому випадку це дивно, оскільки навіть порожня структура повинна давати ненульовий розмір значення ...
WF

2
Цікаво, що якщо ви створите два автоматичні екземпляри, вони зберігатимуться sizeof(int*)окремо (припускаю): coliru.stacked-crooked.com/a/e9a3038bf587b65c
eerorika

9
Це лише відповідає, що масив нульового розміру нестандартний, але це не пояснює запитання, яке ви задали.
хакі

1
Поведінка не повинна мати жодного сенсу відповідно до стандарту - вона повинна мати сенс лише для реалізаторів компілятора. Компілятор вже дозволяє визначати тип у спосіб, заборонений стандартом. Як такі, вимоги стандарту щодо того, що sizeofдає результат для цього типу, також не застосовуються. Отже, поведінка - це рішення, прийняте розробниками компілятора.
Петро,

1
Ідея, 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) */звичайно, це не є стандартно сумісним! (Це може бути використано для легкого синтаксичного аналізу мережевих повідомлень динамічної довжини шляхом перекидання до структури, наприклад)
hoffmale

19

У C ++ масив нульового розміру є незаконним.

ISO / IEC 14882: 2003 8.3.4 / 1:

[..] Якщо присутній константний вираз (5.19), він повинен бути інтегральним постійним виразом, і його значення має бути більше нуля . Постійний вираз визначає межу масиву (кількість елементів у ньому). Якщо значення виразу константи N, масив має Nелементи пронумеровані , 0щоб N-1і тип ідентифікатора Dє « похідним описателем-типу список масиву NT». [..]

g ++ вимагає, щоб -pedanticпрапор давав попередження щодо масиву нульового розміру.


5

Масиви нульової довжини є продовженням GCC та Clang. Застосування sizeofдо масивів нульової довжини обчислюється нулем .

Клас C ++ (порожній) не може мати розмір 0, але зверніть увагу, що клас ZeroMemoryне є порожнім. Він має іменованого члена з розміром, 0і застосування sizeofповерне нуль.


5
так що ... порожній клас не може мати розмір 0, але непорожній клас може мати розмір 0... це не має великого сенсу. Але я думаю, це такий різновид суперечливого випадку, який ви отримуєте з нестандартними розширеннями.
bolov

@bolov; Клас c ++ (порожній) не може мати розмір 0 відповідно до стандарту для порожнього класу. Стандарт C ++ говорить, що оскільки немає іншого способу зробити структуру розміру 0. У цьому конкретному випадку член struct it self має розмір 0, і це робить розмір struct 0. Я знаю, що це бентежить, але я намагався з усіх сил пояснити.
хакі

3
@bodov: Але це не клас C ++, це клас G ++, тому правила C ++ не повинні застосовуватися до нього. Особливо зауважте, що ви не можете мати масив цього типу або вести математику з його покажчиками, тому звичайні міркування про те, що розмір не може бути нульовим, теж не відповідають.
Ben Voigt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.