Ya, __attribute__((packed))
berpotensi tidak aman di beberapa sistem. Gejala ini mungkin tidak akan muncul di x86, yang hanya membuat masalah lebih berbahaya; pengujian pada sistem x86 tidak akan mengungkapkan masalah. (Pada x86, akses yang tidak selaras ditangani dalam perangkat keras; jika Anda men-referensi int*
pointer yang menunjuk ke alamat ganjil, itu akan sedikit lebih lambat daripada jika itu benar selaras, tetapi Anda akan mendapatkan hasil yang benar.)
Pada beberapa sistem lain, seperti SPARC, mencoba mengakses int
objek yang tidak selaras menyebabkan kesalahan bus, menabrak program.
Ada juga sistem di mana akses yang tidak selaras diam-diam mengabaikan bit urutan rendah dari alamat, menyebabkannya mengakses potongan memori yang salah.
Pertimbangkan program berikut:
#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;
}
Pada x86 Ubuntu dengan gcc 4.5.2, ia menghasilkan output berikut:
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
Pada SPARC Solaris 9 dengan gcc 4.5.1, ini menghasilkan yang berikut:
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
Dalam kedua kasus, program dikompilasi tanpa opsi tambahan, adil gcc packed.c -o packed
.
(Program yang menggunakan struct tunggal daripada array tidak menunjukkan masalah, karena kompiler dapat mengalokasikan struct pada alamat ganjil sehingga x
anggota disejajarkan dengan benar. Dengan array dua struct foo
objek, setidaknya satu atau yang lain akan memiliki anggota yang tidak selaras x
.)
(Dalam hal ini, p0
menunjuk ke alamat yang tidak selaras, karena menunjuk ke anggota yang dikemas int
mengikuti char
anggota. p1
Kebetulan disejajarkan dengan benar, karena menunjuk ke anggota yang sama dalam elemen kedua array, sehingga ada dua char
objek sebelumnya - dan pada SPARC Solaris, array arr
tampaknya dialokasikan pada alamat yang genap, tetapi bukan kelipatan 4.)
Ketika merujuk ke x
anggota struct foo
dengan nama, kompiler tahu bahwa x
berpotensi berpotensi tidak selaras, dan akan menghasilkan kode tambahan untuk mengaksesnya dengan benar.
Setelah alamat arr[0].x
atau arr[1].x
telah disimpan dalam objek pointer, baik kompiler maupun program yang berjalan tidak tahu bahwa itu menunjuk ke int
objek yang tidak selaras . Itu hanya mengasumsikan bahwa itu benar selaras, menghasilkan (pada beberapa sistem) kesalahan bus atau kegagalan lainnya yang serupa.
Memperbaiki ini dalam gcc, saya percaya, tidak praktis. Sebuah solusi umum akan membutuhkan, untuk setiap upaya untuk melakukan dereferensi pointer ke jenis apa pun dengan persyaratan penyelarasan non-trivial baik (a) membuktikan pada waktu kompilasi bahwa pointer tidak menunjuk ke anggota yang tidak selaras dari struct yang dikemas, atau (b) menghasilkan kode bulkier dan lebih lambat yang dapat menangani objek yang disejajarkan atau tidak selaras.
Saya telah mengirimkan laporan bug gcc . Seperti yang saya katakan, saya tidak percaya itu praktis untuk memperbaikinya, tetapi dokumentasi harus menyebutkannya (saat ini tidak).
UPDATE : Pada 2018-12-20, bug ini ditandai sebagai TETAP. Tambalan akan muncul di gcc 9 dengan penambahan -Waddress-of-packed-member
opsi baru , diaktifkan secara default.
Ketika alamat anggota struct atau gabungan yang diambil diambil, itu dapat menghasilkan nilai penunjuk yang tidak selaras. Patch ini menambahkan -Waddress-of-packed-member untuk memeriksa perataan pada penugasan pointer dan memperingatkan alamat yang tidak selaras serta pointer yang tidak selaras
Saya baru saja membangun versi gcc dari sumber. Untuk program di atas, ini menghasilkan diagnostik ini:
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;
| ^~~~~~~~~