Có, __attribute__((packed))
có khả năng không an toàn trên một số hệ thống. Triệu chứng có thể sẽ không xuất hiện trên x86, điều này chỉ khiến vấn đề trở nên ngấm ngầm hơn; thử nghiệm trên các hệ thống x86 sẽ không tiết lộ vấn đề. (Trên x86, truy cập lệch được xử lý trong phần cứng, nếu bạn dereference một int*
con trỏ trỏ đến một địa chỉ lẻ, nó sẽ là một chút chậm hơn so với nếu nó được liên kết đúng, nhưng bạn sẽ nhận được kết quả chính xác.)
Trên một số hệ thống khác, chẳng hạn như SPARC, cố gắng truy cập một int
đối tượng sai lệch gây ra lỗi xe buýt, làm hỏng chương trình.
Cũng có những hệ thống trong đó một truy cập sai lệch lặng lẽ bỏ qua các bit thứ tự thấp của địa chỉ, khiến nó truy cập vào đoạn bộ nhớ sai.
Hãy xem xét chương trình sau:
#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;
}
Trên x86 Ubuntu với gcc 4.5.2, nó tạo ra đầu ra sau:
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
Trên SPARC Solaris 9 với gcc 4.5.1, nó tạo ra các mục sau:
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
Trong cả hai trường hợp, chương trình được biên dịch không có tùy chọn bổ sung, chỉ gcc packed.c -o packed
.
(Một chương trình sử dụng một cấu trúc đơn thay vì mảng không thể hiện vấn đề một cách đáng tin cậy, vì trình biên dịch có thể phân bổ cấu trúc trên một địa chỉ lẻ để x
thành viên được căn chỉnh chính xác. Với một mảng gồm hai struct foo
đối tượng, ít nhất là một hoặc hai đối tượng khác sẽ có một x
thành viên sai lệch .)
(Trong trường hợp này, p0
trỏ đến một địa chỉ sai, bởi vì nó trỏ đến một int
thành viên được đóng gói theo sau một char
thành viên. Tình p1
cờ được căn chỉnh chính xác, vì nó trỏ đến cùng một thành viên trong phần tử thứ hai của mảng, vì vậy có hai char
đối tượng trước nó - và trên SPARC Solaris, mảng arr
dường như được phân bổ tại một địa chỉ chẵn, nhưng không phải là bội số của 4.)
Khi đề cập đến thành viên x
của một struct foo
tên, trình biên dịch biết rằng x
có khả năng bị sai lệch và sẽ tạo mã bổ sung để truy cập chính xác.
Khi địa chỉ của arr[0].x
hoặc arr[1].x
đã được lưu trữ trong một đối tượng con trỏ, cả trình biên dịch và chương trình đang chạy đều không biết rằng nó trỏ đến một int
đối tượng sai . Nó chỉ giả định rằng nó được căn chỉnh chính xác, dẫn đến (trên một số hệ thống) bị lỗi bus hoặc lỗi tương tự khác.
Sửa lỗi này trong gcc, tôi tin, là không thực tế. Một giải pháp chung sẽ yêu cầu, cho mỗi lần cố gắng hủy bỏ một con trỏ tới bất kỳ loại nào với các yêu cầu căn chỉnh không tầm thường hoặc (a) chứng minh tại thời điểm biên dịch rằng con trỏ không trỏ đến một thành viên bị sai lệch của cấu trúc đóng gói, hoặc (b) tạo mã lớn hơn và chậm hơn có thể xử lý các đối tượng được căn chỉnh hoặc sắp xếp sai.
Tôi đã gửi một báo cáo lỗi gcc . Như tôi đã nói, tôi không tin rằng việc sửa nó là thực tế, nhưng tài liệu nên đề cập đến nó (hiện tại không có).
CẬP NHẬT : Kể từ 2018-12-20, lỗi này được đánh dấu là CỐ ĐỊNH. Bản vá sẽ xuất hiện trong gcc 9 với việc bổ sung -Waddress-of-packed-member
tùy chọn mới , được bật theo mặc định.
Khi địa chỉ của thành viên đóng gói của struct hoặc union được lấy, nó có thể dẫn đến một giá trị con trỏ không được sắp xếp. Bản vá này thêm -Waddress-of-thành viên đóng gói để kiểm tra căn chỉnh tại gán con trỏ và cảnh báo địa chỉ không được phân bổ cũng như con trỏ không được chỉ định
Tôi vừa mới xây dựng phiên bản gcc đó từ nguồn. Đối với chương trình trên, nó tạo ra các chẩn đoán:
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;
| ^~~~~~~~~