Да, __attribute__((packed))
потенциально небезопасен в некоторых системах.Симптом, вероятно, не появится на x86, что только делает проблему более коварной;тестирование на системах x86 не выявит проблемы.(На x86 неправильно выровненный доступ обрабатывается аппаратно; если вы разыменуете указатель int*
, указывающий на нечетный адрес, он будет немного медленнее, чем если бы он был правильно выровнен, но вы получите правильный результат.)
В некоторых других системах, таких как SPARC, попытка получить доступ к неправильно выровненному объекту int
вызывает ошибку шины, вызывая сбой программы.
Также были системы, в которых неправильно выровненный доступ тихо игнорируетсямладшие биты адреса, из-за чего он получает доступ к неправильному фрагменту памяти.
Рассмотрим следующую программу:
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
В Ubuntu x86 с gcc 4.5.2 он выдаетследующий вывод:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
В SPARC Solaris 9 с gcc 4.5.1 выдает следующее:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
В обоих случаях программа компилируется без дополнительных опций,просто gcc packed.c -o packed
.
(Программа, которая использует одну структуру, а не массив, надежно не демонстрирует проблему, так как компилятор может разместить структуру по нечетному адресу, такx
элемент правильно выровнен.С массивом из двух struct foo
объектов, по крайней мере, один или другой будет иметь неправильно выровненный x
член.)
(В этом случае p0
указывает на смещенный адрес, потому что он указывает наупакованный элемент int
, следующий за элементом char
. p1
оказывается правильно выровненным, поскольку он указывает на тот же элемент во втором элементе массива, поэтому перед ним стоят два объекта char
- ив SPARC Solaris массив arr
представляется расположенным по четному, но не кратному 4 адресу.)
При обращении к элементу x
из struct foo
по имени,Компилятор знает, что x
потенциально не выровнен, и будет генерировать дополнительный код для правильного доступа к нему.
Как только адрес arr[0].x
или arr[1].x
сохранен в объекте указателя, ни компилятор, низапущенная программа знает, что она указывает на смещенный объект int
.Это просто предполагает, что он правильно выровнен, что приводит (в некоторых системах) к ошибке шины или аналогичной другой ошибке.
Исправить это в gcc, я считаю, было бы нецелесообразно.Общее решение потребовало бы для каждой попытки разыменования указателя на любой тип с нетривиальными требованиями выравнивания либо (а) доказать во время компиляции, что указатель не указывает на неправильно выровненный элемент упакованной структуры, либо (б)создание более объемного и более медленного кода, который может обрабатывать либо выровненные, либо выровненные объекты.
Я отправил gcc отчет об ошибке .Как я уже сказал, я не думаю, что это практично исправить, но в документации следует упомянуть об этом (в настоящее время это не так).
ОБНОВЛЕНИЕ : по состоянию на 2018-12-20эта ошибка помечена как ИСПРАВЛЕННАЯ.Патч появится в gcc 9 с добавлением новой опции -Waddress-of-packed-member
, включенной по умолчанию.
Если адрес упакованного члена структуры или объединения взят, это может привести к не выровненному указателюзначение.Этот патч добавляет -Waddress-of-pack-member для проверки выравнивания при назначении указателя и предупреждения о не выровненном адресе, а также о невыровненном указателе
Я только что собрал эту версию gcc из исходного кода.Для вышеприведенной программы она производит следующие диагностики:
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~