1

Не совсем понимаю, что происходит при упаковке, например, структур.

Допустим:

#include <stdio.h>
#include <stdlib.h>

struct s
{
    int i;
    char c;
} __attribute__ ((__packed__));

int main()
{
    struct s arr[2];

    printf("sizeof(s): %Iu\n", sizeof(struct s));
    printf("sizeof(arr): %Iu\n", sizeof(arr));

    printf("%p\n", &(arr[1].i));

    return 0;
}

Вывод:

sizeof(s): 5
sizeof(arr): 10
0060FEFB

Получается, что начиная arr[1] поле i имеет неверное выравнивание, потому что адрес 0060FEFB не кратен sizeof(int). А согласно стандарту, работа с невыровненными данными - это неопределенное поведение. Например, если мы обратимся такому к полю i через указатель (->, *), то получим неопределенное поведение.

MGNeo
  • 4,128
  • Он остаётся без изменений. Т.е. char дополняется до размера sizeof(int), а int уже имеет такой размер, поэтому его просто не нужно выравнивать. – nick_n_a Mar 06 '19 at 13:52
  • А если i c поменять местами, то в неупакованой структуре с будет иметь адрес +0, и будет дополнен, а i будет иметь адрес +4. Если не менять - то же самое, один элемент будет иметь адрес +0, а другой +4 из-за выравнивания. – nick_n_a Mar 06 '19 at 13:55
  • @nick_n_a, char никуда не дополняется – Pavel Gridin Mar 06 '19 at 15:00

1 Answers1

3

Доступ к невыровненым данным формально приводит к неопределенному поведению. Однако С/С++ реализации могут предоставлять такой доступ в качестве расширения. При этом реализации делятся на несколько типов по отношению к выравниванию.

  1. Выравнивание требуется на уровне аппаратуры. Реализация не предпринимает никаких мер для обхода требований аппаратуры. Попытка доступа к невыровненным данным приводит к аварийному завершению программы.

  2. Выравнивание требуется на уровне аппаратуры. Реализация выполняет безопасный (по частям) доступ к потенциально невыровненным данным. Доступ к потенциально невыровненным данным возможен, но существенно менее производителен.

  3. Выравнивание не требуется на уровне аппаратуры. Реализации не надо ни о чем беспокоиться. Доступ к потенциально невыровненным данным возможен, но обычно несколько менее эффективен.

Вы скорее всего имеете дело с реализацией типа 3.

См. также О чём говорит предупреждение о выравнивании данных?

  • А разве сам Стандарт не говорит о том, что работа с невыровненными данными, - это неопределенное поведение на уровне языка, независимо от того, позволяет ли целевая платформа работать с такими данными или нет? – MGNeo Mar 06 '19 at 14:41
  • Стандарт не может знать на какой ЦП и OS вы работаете. – nick_n_a Mar 06 '19 at 14:48
  • @nick_n_a, вот именно. Поэтому в стандарте говорится, что нельзя работать с неверно выровненными данными. Конкретно, оператор * имеет неопределенное поведение, если указатель указывает данные, выравнивание которых не соответствует требованиям целевого типа. – MGNeo Mar 06 '19 at 14:50
  • @MGNeo, это что за стандарт такой? – Pavel Gridin Mar 06 '19 at 14:59
  • C99: 6.5.3.2 Адрес и операция разыменования Если указателю было присвоено недопустимое значение, поведение унарного оператора * не определено.
    1. Среди недопустимых значений для разыменования оператора унарным оператором *: пустой указатель; ненадлежащим образом выровненный адрес для типа указываемого объекта; адрес объекта по окончании его использования
    – MGNeo Mar 06 '19 at 15:03
  • @MGNeo Стандарт говорит, что объекты должны создаваться в памяти, которая корректно выровнена ("properly aligned"). Что именно означает это "корректно" и где начинается "некорректно" - определяется реализацией. – AnT stands with Russia Mar 06 '19 at 15:03
  • Рассмотрим мой пример. Если структуру упаковать, и сделать массив из >= двух элементов, то начиная со второго элемента массива поле int i окажется неверно выровнено, потому что размер упакованной структуры = 5, а размер массива из двух элементов = 10. Может, я просто это неверно понимаю? – MGNeo Mar 06 '19 at 15:06
  • 1
    @AnT, само выравнивание (количественно) определяется реализацией, а вот наличие корректного выравнивания стандартом требуется. Т.е. если alignof(int) == 4, а размещён он чёрти где, то это нарушение стандарта. – ixSci Mar 06 '19 at 15:07
  • @AnT, в моем примере поле i второго элемента массива, судя по всему, будет валяться по адресу, не кратному sizeof(int). Как это может быть корректным? – MGNeo Mar 06 '19 at 15:12
  • @MGNeo, в стандарте нет __attribute__((__packed__)), поэтому использование таких вещей на свой страх и риск. Возможно, GCC как-то по особому с такими типами работает и не даст нарваться на неприятности на платформах, где они возможны, а возможно — нет. – ixSci Mar 06 '19 at 15:16
  • 3
    @MGNeo, обсуждать поведение кода с нестандартным __attribute__((__packed__)) в рамках стандарта довольно бессмысленно... говоря packed программист сам берёт на себя работу по выравниванию для сборки под целевую архитектуру. – Fat-Zer Mar 06 '19 at 15:19
  • Дополнил вопрос. – MGNeo Mar 06 '19 at 15:19
  • @MGNeo AnT имеет в виду, что в стандарте не сказано, что alignof возвращает "минимальное необходимое" выравнивание. Он запросто может возвращать "оптимальное" выраванивание. – HolyBlackCat Mar 06 '19 at 15:19
  • win98 при невыравняном обращении при записи двойного слова с выходом за пределы страницы 4К - падал в креш (синий экран). На современных - давно не проверял. – nick_n_a Mar 06 '19 at 15:25
  • @AnT, в приведенной вами ссылке рассматривается не совсем то, что я имею в виду. – MGNeo Mar 06 '19 at 15:33
  • @ixSci Да, вы правы. Действительно поведение не определено. В таком случае поддержка невыровненного доступа является чистым расширением, поддерживаемым данной реализацией. – AnT stands with Russia Mar 06 '19 at 16:32