Có một cách tiêu chuẩn hoặc tiêu chuẩn thay thế để đóng gói một cấu trúc trong c?


13

Khi lập trình trong CI đã thấy rằng việc đóng gói các cấu trúc bằng __attribute__((__packed__))thuộc tính GCCs là vô giá, vì vậy tôi có thể dễ dàng chuyển đổi một khối bộ nhớ dễ bay hơi có cấu trúc thành một mảng byte được truyền qua một bus, được lưu vào bộ lưu trữ hoặc áp dụng cho một khối các thanh ghi. Các cấu trúc được đóng gói đảm bảo rằng khi được coi là một mảng byte, nó sẽ không chứa bất kỳ phần đệm nào, điều này vừa lãng phí, có thể có rủi ro bảo mật và có thể không tương thích khi giao tiếp với phần cứng.

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? Nếu không thì tôi là một ngoại lệ trong suy nghĩ đây là một tính năng quan trọng cho lập trình hệ thống? Có phải những người sử dụng sớm ngôn ngữ C không tìm thấy nhu cầu đóng gói cấu trúc hoặc có một số loại thay thế?


sử dụng cấu trúc trên các miền biên dịch là một ý tưởng rất tồi, đặc biệt là chỉ vào phần cứng (là một miền biên dịch khác). gói cấu trúc chỉ là một mẹo để thực hiện việc này, chúng có rất nhiều tác dụng phụ xấu, vì vậy có nhiều giải pháp khác cho các vấn đề của bạn với ít tác dụng phụ hơn, và dễ mang theo hơn.
old_timer

Câu trả lời:


12

Trong một cấu trúc, điều quan trọng là phần bù của mỗi thành viên từ địa chỉ của từng thể hiện cấu trúc. Không quá nhiều là vấn đề làm thế nào chặt chẽ mọi thứ được đóng gói.

Một mảng, tuy nhiên, vấn đề là làm thế nào nó được "đóng gói". Quy tắc trong C là mỗi phần tử mảng chính xác là N byte từ trước đó, trong đó N là số byte được sử dụng để lưu trữ loại đó.

Nhưng với một cấu trúc, không có nhu cầu về tính đồng nhất như vậy.

Đây là một ví dụ về sơ đồ đóng gói kỳ lạ:

Freescale (người chế tạo vi điều khiển ô tô) tạo ra một vi mô có bộ đồng xử lý Đơn vị xử lý thời gian (google cho eTPU hoặc TPU). Nó có hai kích thước dữ liệu riêng, 8 bit và 24 bit và chỉ giao dịch với số nguyên.

Cấu trúc này:

struct a
{
  U24 elementA;
  U24 elementB;
};

sẽ thấy mỗi U24 được lưu trữ khối 32 bit của riêng mình, nhưng chỉ trong khu vực địa chỉ cao nhất.

Điều này:

struct b
{
  U24 elementA;
  U24 elementB;
  U8  elementC;
};

sẽ có hai U24 được lưu trữ trong các khối 32 bit liền kề và U8 sẽ được lưu trữ trong "lỗ" phía trước U24 đầu tiên , elementA.

Nhưng bạn có thể yêu cầu trình biên dịch đóng gói mọi thứ vào khối 32 bit của riêng nó, nếu bạn muốn; Nó đắt hơn trên RAM nhưng sử dụng ít hướng dẫn hơn để truy cập.

"Đóng gói" không có nghĩa là "đóng gói chặt chẽ" - nó chỉ có nghĩa là một số sơ đồ để sắp xếp các phần tử của một cấu trúc bù đắp.

Không có sơ đồ chung, nó phụ thuộc vào trình biên dịch + kiến ​​trúc.


1
Nếu trình biên dịch cho TPU sắp xếp lại struct bđể di chuyển elementCtrước bất kỳ phần tử nào khác, thì đó không phải là trình biên dịch C phù hợp.
Sắp

Thú vị nhưng U24 không phải là loại C tiêu chuẩn en.m.wikipedia.org/wiki/C_data_types nên không có gì đáng ngạc nhiên khi trình biên dịch buộc phải xử lý theo cách hơi kỳ quặc.
satur9nine

Nó chia sẻ RAM với lõi CPU chính có kích thước từ 32 bit. Nhưng bộ xử lý này có ALU chỉ xử lý 24 bit hoặc 8 bit. Vì vậy, nó có một sơ đồ để đặt ra các số 24 bit trong các từ 32 bit. Không chuẩn, nhưng một ví dụ tuyệt vời về đóng gói và căn chỉnh. Đồng ý, nó rất không chuẩn.
RichColours

6

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 structmà 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ứ xphả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 Footrườ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ứ.


2
Tôi cho rằng việc có một phương tiện xác định bố cục cấu trúc rõ ràng sẽ giúp tăng cường tính di động một cách ồ ạt . Mặc dù một số bố cục sẽ dẫn đến mã rất hiệu quả trên một số máy và mã rất kém hiệu quả trên các máy khác, mã sẽ hoạt động trên tất cả các máy và sẽ hiệu quả với ít nhất một số máy. Ngược lại, trong trường hợp không có tính năng như vậy, cách duy nhất để làm cho mã hoạt động trên tất cả các máy có thể là làm cho nó không hiệu quả trên tất cả các máy hoặc sử dụng một loạt các macro và biên dịch có điều kiện để kết hợp nhanh không di động chương trình và một chương trình di động chậm trong cùng một nguồn.
supercat

Về mặt khái niệm là có, nếu chúng ta có thể chỉ định mọi thứ theo cách biểu diễn bit và byte, yêu cầu căn chỉnh, độ bền, v.v. và có một tính năng cho phép điều khiển rõ ràng như vậy trong C trong khi tùy ý tách nó ra khỏi kiến ​​trúc cơ bản ... Nhưng tôi chỉ nói về ATM - hiện là giải pháp di động nhất cho bộ nối tiếp là viết nó theo cách sao cho nó không phụ thuộc vào các bit và byte chính xác biểu thị và căn chỉnh các loại dữ liệu. Thật không may, chúng tôi thiếu phương tiện ATM để làm khác một cách hiệu quả (bằng C).

5

Không có tất cả các kiến ​​trúc đều giống nhau, chỉ cần bật tùy chọn 32 bit trên một mô-đun và xem điều gì xảy ra khi sử dụng cùng một mã nguồn và cùng một trình biên dịch. Thứ tự Byte là một hạn chế nổi tiếng khác. Ném vào đại diện điểm nổi và các vấn đề trở nên tồi tệ hơn. Sử dụng Đóng gói để gửi dữ liệu nhị phân là không di động. Để chuẩn hóa nó để nó thực tế có thể sử dụng được, bạn sẽ cần xác định lại đặc tả ngôn ngữ C.

Mặc dù phổ biến, sử dụng Gói để gửi dữ liệu nhị phân là một ý tưởng tồi nếu bạn muốn bảo mật, tính di động hoặc tuổi thọ của dữ liệu. Tần suất bạn đọc một blob nhị phân từ một nguồn vào chương trình của bạn. Tần suất bạn kiểm tra tất cả các giá trị là lành mạnh, rằng tin tặc hoặc thay đổi chương trình không 'có' dữ liệu? Vào thời điểm bạn đã mã hóa một thói quen kiểm tra, bạn cũng có thể sử dụng các thói quen nhập và xuất.


0

Một thay thế rất phổ biến là "đệm có tên":

struct s {
  short s1;
  char  c2;
  char  reserved; // Padding
};

Điều này không cho rằng cấu trúc sẽ không được đệm đến 8 byte.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.