Khi lập trình trong CI đã tìm thấy nó là vô giá để đóng gói các cấu trúc bằng GCCs __attribute__((__packed__))
[...]
Vì bạn đề cập __attribute__((__packed__))
, tôi cho rằng ý định của bạn là loại bỏ tất cả các phần đệm trong một struct
(làm cho mỗi thành viên có căn chỉnh 1 byte).
Không có tiêu chuẩn để đóng gói các cấu trúc hoạt động trong tất cả các trình biên dịch C?
... Và câu trả lời là không". Việc đệm và căn chỉnh dữ liệu liên quan đến một cấu trúc (và các mảng liên tục của các cấu trúc trong ngăn xếp hoặc đống) tồn tại vì một lý do quan trọng. Trên nhiều máy, truy cập bộ nhớ không được phân bổ có thể dẫn đến một hình phạt hiệu năng đáng kể (mặc dù trở nên ít hơn trên một số phần cứng mới hơn). Trong một số trường hợp hiếm gặp, việc truy cập bộ nhớ bị sai lệch dẫn đến lỗi bus không thể phục hồi (thậm chí có thể làm sập toàn bộ hệ điều hành).
Do tiêu chuẩn C tập trung vào tính di động, nên có một cách tiêu chuẩn để loại bỏ tất cả các phần đệm trong cấu trúc và chỉ cho phép các trường tùy ý bị sai lệch, do đó sẽ có nguy cơ khiến mã C không thể di động.
Cách an toàn và di động nhất để xuất dữ liệu đó ra nguồn bên ngoài theo cách loại bỏ tất cả các phần đệm là tuần tự hóa đến / từ các luồng byte thay vì chỉ cố gửi qua nội dung bộ nhớ thô của bạn structs
. Điều đó cũng ngăn chương trình của bạn chịu các hình phạt về hiệu suất bên ngoài bối cảnh tuần tự hóa này và cũng sẽ cho phép bạn tự do thêm các trường mới vào struct
mà không vứt bỏ và làm hỏng toàn bộ phần mềm. Nó cũng sẽ cung cấp cho bạn một số chỗ để giải quyết vấn đề về tuổi thọ và những thứ tương tự nếu điều đó trở thành mối quan tâm.
Có một cách để loại bỏ tất cả phần đệm mà không cần truy cập vào các chỉ thị dành riêng cho trình biên dịch, mặc dù nó chỉ áp dụng nếu thứ tự tương đối giữa các trường không quan trọng. Đưa ra một cái gì đó như thế này:
struct Foo
{
double x; // assume 8-byte alignment
char y; // assume 1-byte alignment
// 7 bytes of padding for first field
};
... Chúng ta cần phần đệm để truy cập bộ nhớ được căn chỉnh liên quan đến địa chỉ của cấu trúc có chứa các trường này, như vậy:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......
... nơi .
chỉ ra phần đệm. Mọi thứ x
phải phù hợp với một ranh giới 8 byte cho hiệu suất (và đôi khi thậm chí là hành vi đúng).
Bạn có thể loại bỏ phần đệm theo cách di động bằng cách sử dụng biểu diễn SoA (cấu trúc của mảng) như vậy (giả sử chúng ta cần 8 Foo
trường hợp):
struct Foos
{
double x[8];
char y[8];
};
Chúng tôi đã phá hủy cấu trúc một cách hiệu quả. Trong trường hợp này, biểu diễn bộ nhớ trở thành như thế này:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______
... và điều này:
01234567
yyyyyyyy
... không còn phần đệm trên đầu và không liên quan đến truy cập bộ nhớ bị sai lệch do chúng ta không còn truy cập vào các trường dữ liệu này dưới dạng phần bù của địa chỉ cấu trúc, mà thay vào đó là phần bù của địa chỉ cơ sở cho mảng thực sự là một mảng.
Điều này cũng mang lại phần thưởng là truy cập tuần tự nhanh hơn do cả hai dữ liệu ít tiêu thụ hơn (không có phần đệm không liên quan trong hỗn hợp để làm chậm tốc độ tiêu thụ dữ liệu có liên quan của máy) và cũng là một tiềm năng để trình biên dịch xử lý véc tơ rất tầm thường .
Nhược điểm là nó là mã Pita. Nó cũng có khả năng kém hiệu quả hơn khi truy cập ngẫu nhiên với bước tiến lớn hơn giữa các trường, nơi thường các đại diện AoS hoặc AoSoA sẽ làm tốt hơn. Nhưng đó là một cách tiêu chuẩn để loại bỏ phần đệm và đóng gói mọi thứ chặt chẽ nhất có thể mà không vặn vẹo với sự liên kết của mọi thứ.