Evet, __attribute__((packed))
bazı sistemlerde potansiyel olarak güvenli değildir. Semptom muhtemelen x86'da görünmeyecek, bu da sorunu daha sinsi hale getiriyor; x86 sistemlerinde test yapmak sorunu ortaya çıkarmaz. (X86'da, yanlış hizalanmış erişimler donanımda işlenir; int*
tek bir adrese işaret eden bir işaretçiyi kaldırırsanız , düzgün hizalanmış olduğundan biraz daha yavaş olur, ancak doğru sonucu alırsınız.)
SPARC gibi diğer bazı sistemlerde, yanlış hizalanmış bir int
nesneye erişmeye çalışmak , programın çökmesine neden olan bir veriyolu hatasına neden olur.
Yanlış hizalanmış bir erişimin adresin düşük dereceli bitlerini sessizce göz ardı ettiği ve yanlış bellek yığınına erişmesine neden olan sistemler de vardır.
Aşağıdaki programı düşünün:
#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;
}
Gcc 4.5.2 ile x86 Ubuntu'da aşağıdaki çıktıyı üretir:
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
Gcc 4.5.1 ile SPARC Solaris 9'da aşağıdakileri üretir:
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
Her iki durumda da, program sadece ekstra seçeneklerle derlenir gcc packed.c -o packed
.
(Bu nedenle derleyici tek adresine yapı tahsis çünkü, dizi güvenilir bir sorunu gösteren yerine tek bir yapı kullanan bir program x
iki Bir dizi elemanı uygun şekilde hizalanır. struct foo
Nesneler, en az bir ya da diğer yanlış hizalanmış bir x
üyesi olacaktır.)
(Bu durumda, p0
yanlış hizalanmış bir adrese işaret eder, çünkü bir int
üyeyi izleyen paketlenmiş bir char
üyeye p1
işaret eder. Dizinin ikinci öğesinde aynı üyeye işaret ettiğinden, doğru hizalanmış olur, bu nedenle ondan char
önce iki nesne vardır - ve SPARC Solaris'te dizi arr
eşit olan, ancak 4'ün katı olmayan bir adede tahsis edilmiş gibi görünür.)
Üyesi olduğu ifade edilirken x
a struct foo
adıyla, derleyici bilir x
potansiyel yanlış hizalanmış olup, doğru şekilde erişmek için ek kod üretecektir.
İşaretçi nesnesinin adresi arr[0].x
veya arr[1].x
bir nesne içinde saklandıktan sonra, ne derleyici ne de çalışan program yanlış hizalanmış bir int
nesneyi gösterdiğini bilmez . Sadece düzgün bir şekilde hizalandığını varsayar (bazı sistemlerde) bir veri yolu hatası veya benzeri başka bir arızaya neden olur.
Bunu gcc'de düzeltmek, pratik olmadığına inanıyorum. Genel bir çözüm, her bir işaretçiyi önemsiz hizalama gerekliliklerine sahip herhangi bir türden vazgeçirmek için ya (a) derleyicinin paketlenmiş bir yapının yanlış hizalanmış bir üyesine işaret etmediğini kanıtlaması veya (b) hizalanmış veya yanlış hizalanmış nesneleri işleyebilen daha büyük ve yavaş kod üretme.
Bir gcc hata raporu gönderdim . Dediğim gibi, düzeltmenin pratik olduğuna inanmıyorum, ancak belgeler bundan bahsetmelidir (şu anda değil).
GÜNCELLEME : 2018-12-20 itibariyle bu hata DÜZELTİL olarak işaretlenir. Yama, -Waddress-of-packed-member
varsayılan olarak etkin olan yeni bir seçenek eklenerek gcc 9'da görünecektir .
Paketlenmiş yapı veya birleşim üyesinin adresi alındığında, hizalanmamış işaretçi değeri ile sonuçlanabilir. Bu düzeltme eki, işaretçi atamasındaki hizalamayı denetlemek ve hizalanmamış adresin yanı sıra hizalanmamış işaretçiyi uyarmak için paketlenmiş üyeye eklenir.
Gcc'nin bu sürümünü kaynağından oluşturdum. Yukarıdaki program için şu teşhisleri üretir:
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;
| ^~~~~~~~~