Что представляет собой прокладка в союзе? - PullRequest
0 голосов
/ 12 января 2019

Я пытаюсь интерпретировать стандарт C11 , касающийся статической (и локальной для потока) инициализации объединения, если он не был явно инициализирован.

Раздел 6.7.9 10 (стр. 139) гласит следующее:

Если объект, имеющий автоматическую продолжительность хранения, не инициализируется явно, его значение является неопределенным. Если объект со статической или потоковой длительностью хранения не инициализирован явно, то:

- если он имеет тип указателя, он инициализируется нулевым указателем;

- если он имеет арифметический тип, он инициализируется равным (положительному или без знака) нулю;

- если это агрегат, каждый элемент инициализируется (рекурсивно) в соответствии с этими правилами, а любое заполнение инициализируется нулевыми битами;

- если это объединение, первый именованный элемент инициализируется (рекурсивно) в соответствии с этими правилами, а любое заполнение инициализируется нулевыми битами;

Предположим, мы находимся на архитектуре amd64, учитывая следующее утверждение:

static union { uint32_t x; uint16_t y[3]; } u;

Может ли u.y[2] содержать ненулевые значения или оно инициализируется нулем, потому что рассматривается как заполнитель?

Я изучил стандарт C11, но мало что объясняет, что такое заполнение в объединении. В стандарте C99 (стр. 126) заполнение не упоминается, поэтому в этом случае u.y[2] может быть ненулевым.

Ответы [ 3 ]

0 голосов
/ 13 января 2019

Дополнительный пробел, используемый y, который не используется x, не считается заполнением. Раздел 6.7.2.1p17 стандарта C11 относительно состояний "Спецификаторы структуры и объединения":

Там может быть безымянный отступ в конце структуры или объединения

Байты, используемые y в вашем примере, которые не используются x, по-прежнему имеют имена и, следовательно, не дополняются.

В вашем примере, скорее всего, имеет этот безымянный заполнитель, так как самый большой элемент занимает 6 байтов, но один из членов - uint32_t, который обычно требует выравнивания 4 байтов. На самом деле, на gcc 4.8.5 размер этого объединения составляет 8 байт. Итак, расположение памяти этого объединения выглядит так:

            -----  --|       ---|
         0  | 0 |    |          |
            -----    |          |-- y[0]
         1  | 0 |    |          |
            -----    |-- x   ---|
         2  | 0 |    |          |            
            -----    |          |-- y[1]
         3  | 0 |    |          |
            -----  --|       ---|
         4  | 0 |               |
            -----               |-- y[2]
         5  | 0 |               |
            -----            ---|
         6  | 0 |  -- padding
            -----
         7  | 0 |  -- padding
            -----

Таким образом, строго придерживаясь стандарта, для статического экземпляра этого объединения без явного инициализатора:

  • Байты 0–3, соответствующие x (то есть первому названному элементу), инициализируются равными 0, в результате чего x равно 0.
  • Байты 4 - 5, соответствующие y [2], остаются неинициализированными и имеют неопределенных значений .
  • Байты 6 - 7, соответствующие заполнению, инициализируются равными 0.

Я протестировал это на gcc 4.8.5, clang 3.3 и MSVC 2015, и все они установили все байтов в 0 при различных настройках оптимизации. Однако при строгом чтении стандарта поведение не гарантируется, поэтому все же возможно, что разные настройки оптимизации этих компиляторов, разных версий или разных компиляторов могут сделать что-то другое.

С прагматической точки зрения для компилятора имеет смысл просто установить все байты статического объекта в 0, чтобы удовлетворить это требование. Это, конечно, при условии, что целочисленные типы не имеют отступов, типы с плавающей точкой - IEEE754, а указатели NULL имеют числовое значение 0. В большинстве систем, с которыми может столкнуться большинство людей, это будет иметь место. Системы, в которых это не так, могут с большей вероятностью оставить для этих байтов значение, отличное от 0. Итак, хотя эти байты могут быть установлены в 0, гарантии нет.

Важно помнить, что профсоюз может хранить только одного члена за раз согласно 6.7.2.1p16:

Размер союза достаточен, чтобы вместить самого большого из его членов. Значение в большинство из членов могут быть сохранены в объекте объединения в любое время. Указатель на Объект объединения, соответствующим образом преобразованный, указывает на каждого из его членов (или, если член поле, затем к единице, в которой он находится), и наоборот.

Таким образом, если union со статической продолжительностью хранения неинициализирован, доступ только к первому члену *1056* безопасен, так как это тот, который был неявно инициализирован.

Единственное исключение из этого, если объединение содержит структуры с общим набором начальных членов, и в этом случае вы можете получить доступ к любому из общих элементов внутренних структур. Это подробно описано в разделе 6.5.2.3p6:

Одна специальная гарантия сделана для того, чтобы упростить использование союзов: если союз содержит несколько структур, которые имеют общую начальную последовательность (см. ниже), и если объединение В настоящее время объект содержит одну из этих структур, разрешается проверять общие Начальная часть любого из них где угодно, что объявление о завершенном типе объединения виден Две структуры разделяют общая начальная последовательность если соответствующие члены иметь совместимые типы (и, для битовых полей, одинаковой ширины) для последовательности из одного или нескольких первоначальные члены.

0 голосов
/ 13 января 2019

Can u.y[2] contain non-zero values or is it initialised to zero because it is regarded as padding?

u.y[2] не считается заполнением. Это элемент массива y, который является членом объединения u.

Объединение имеет настолько большой размер, насколько необходимо для удержания его самого большого элемента (дополнительные неназванные завершающие отступы также могут быть добавлены для выравнивания ).

Из стандарта C # 6.7.2.1p17

17 Там может быть безымянный отступ в конце структуры или объединения.

Самый большой член союза u - uint16_t y[3];. Таким образом, если в объединении u есть отступы, то это будет после uint16_t y[3]; member 1) .

В соответствии со Стандартом C11, объект объединения, имеющий статическую длительность или длительность хранения и не инициализированный явно, компилятор должен инициализировать первый именованный элемент (рекурсивно) и любое заполнение до нуля битов. Следовательно, вы не должны делать никаких предположений о значении u.y[2], потому что компилятор будет только инициализировать первый именованный элемент объединения 2) , что в вашем примере равно uint32_t x, и любой заполнение нулевыми битами (# 6.7.9p10).

C Стандарт не упоминает ничего о сегменте данных (инициализированный / неинициализированный), стеке, куче и т. Д. Все они зависят от архитектуры / платформы. Для инициализации объекта Стандарты C указывают только то, что нужно инициализировать до 0, а что нет, и не указывает, какой объект продолжительности хранения идет в какой сегмент. Стандартные спецификации предназначены для компиляторов, и ожидается, что за ними последует хороший компилятор. Обычно инициализированные статические данные 0 помещаются в .BSS (блок, начатый символом), а инициализированные данные 0 - в .DATA (сегмент данных). Таким образом, вы можете найти u.y[2] значение 0, но это не всегда так.


1) Каждый современный компилятор автоматически использует заполнение структуры данных в зависимости от архитектуры. Некоторые компиляторы даже поддерживают флаг предупреждения -Wpadded, который генерирует полезные предупреждения о заполнении структуры. Эти предупреждения помогают программисту соблюдать осторожность в случае, если требуется более эффективная структура структуры данных.

-Wpadded

Предупреждать, если в структуру включено заполнение, либо для выравнивания элемента структуры, либо для выравнивания всей структуры. Иногда, когда это происходит, можно изменить поля структуры, чтобы уменьшить заполнение и таким образом уменьшить конструкцию.

Итак, если ваш компилятор поддерживает флаг предупреждения -Wpadded, попробуйте скомпилировать свой код с ним. Это поможет вам понять заполнение, включенное компилятором.

Например,

#include <inttypes.h>

int main() {
        static union { uint32_t x; uint16_t y[3]; } u;
}

Позволяет скомпилировать это с опцией -Wpadded. Мой компилятор clang версия clang-1000.10.44.4

# clang -Wpadded p.c

p.c:4:16: warning: padding size of 'union (anonymous at p.c:4:16)' with 2 bytes to alignment boundary [-Wpadded]
        static union { uint32_t x; uint16_t y[3]; } u;
               ^
1 warning generated.

2) Обратите внимание: если вы явно инициализируете объект объединения, если только он не был назначен, то будет инициализирован первый член объединения (C11 Standard # 6.7.9p17).

0 голосов
/ 12 января 2019

Если хранилище автоматическое, оно может содержать любое значение, поскольку оно не инициализировано. Если хранилище статическое, оно будет инициализировано нулями.

Заполнение не влияет на ваш союз, поскольку он не принадлежит ни одному из членов структуры или союза.

Например, если в вашей реализации данные дополняются до границы 8 байтов, заполнение не будет добавлено вообще. Между этим объединением и следующим объектом будет разрыв в 2 байта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...