Так, __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;
}
На x86 Ubuntu з 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 було б, я вважаю, недоцільно. Загальне рішення вимагає для кожної спроби скинути вказівник на будь-який тип з нетривіальними вимогами до вирівнювання або (a) доведення під час компіляції, що покажчик не вказує на несогласованного члена упакованої структури, або (b) генеруючи більш об'ємний і повільний код, який може обробляти або вирівняні або нерівні об'єкти.
Я надіслав звіт про помилку 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;
| ^~~~~~~~~