C / C ++: Bắt buộc thứ tự trường bit và căn chỉnh


87

Tôi đọc rằng thứ tự của các trường bit trong một cấu trúc là nền tảng cụ thể. Còn nếu tôi sử dụng các tùy chọn đóng gói dành riêng cho trình biên dịch khác nhau, liệu dữ liệu đảm bảo này có được lưu trữ theo thứ tự thích hợp khi chúng được viết không? Ví dụ:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

Trên bộ xử lý Intel với trình biên dịch GCC, các trường được sắp xếp trong bộ nhớ như chúng được hiển thị. Message.versionlà 3 bit đầu tiên trong bộ đệm và Message.typetiếp theo. Nếu tôi tìm thấy các tùy chọn đóng gói cấu trúc tương đương cho các trình biên dịch khác nhau, thì đây có phải là nền tảng chéo không?


17
Vì bộ đệm là một tập hợp các byte, không phải các bit, nên "3 bit đầu tiên trong bộ đệm" không phải là một khái niệm chính xác. Bạn sẽ coi 3 bit bậc thấp nhất của byte đầu tiên là 3 bit đầu tiên hay 3 bit bậc cao nhất?
caf 29/09/09

2
Khi chuyển tiếp trên mạng, "3 bit đầu tiên trong bộ đệm" hóa ra được xác định rất rõ.
Joshua

2
@Joshua IIRC, Ethernet truyền bit quan trọng nhất của mỗi byte đầu tiên (đó là lý do tại sao bit quảng bá ở vị trí của nó).
tc.

Khi bạn nói "di động" và "đa nền tảng", bạn có nghĩa là gì? Tập tin thực thi sẽ truy cập chính xác thứ tự bất kể hệ điều hành mục tiêu - hoặc - mã sẽ biên dịch bất kể chuỗi công cụ?
Garet Claborn

Câu trả lời:


103

Không, nó sẽ không hoàn toàn di động. Các tùy chọn đóng gói cho cấu trúc là phần mở rộng và bản thân nó không hoàn toàn di động. Ngoài ra, C99 §6.7.2.1, đoạn 10 nói: "Thứ tự phân bổ các trường bit trong một đơn vị (từ bậc cao đến bậc thấp hoặc từ bậc thấp đến bậc cao) được xác định bởi sự thực thi."

Ví dụ, ngay cả một trình biên dịch đơn lẻ cũng có thể bố trí trường bit khác nhau tùy thuộc vào độ bền của nền tảng đích.


Vâng, GCC, ví dụ, đặc biệt lưu ý rằng các trường bit được sắp xếp theo ABI, không phải việc triển khai. Vì vậy, chỉ ở trên một trình biên dịch duy nhất là không đủ để đảm bảo việc đặt hàng. Kiến trúc cũng phải được kiểm tra. Thực sự là một cơn ác mộng về tính di động.
underscore_d

10
Tại sao tiêu chuẩn C không đảm bảo thứ tự cho các trường bit?
Aaron Campbell

7
Thật khó để xác định một cách nhất quán và linh hoạt "thứ tự" của các bit trong byte, ít hơn nhiều thứ tự của các bit có thể vượt qua ranh giới byte. Bất kỳ định nghĩa nào mà bạn giải quyết sẽ không phù hợp với một lượng đáng kể thực tiễn hiện có.
Stephen Canon

2
do implementaiton xác định cho phép tối ưu hóa nền tảng cụ thể. Trên một số nền tảng, việc đệm giữa các trường bit có thể cải thiện khả năng truy cập, hãy tưởng tượng bốn trường bảy bit trong một int 32 bit: căn chỉnh chúng ở mỗi bit thứ 8 là một cải tiến đáng kể cho các nền tảng có đọc byte.
peterchen


45

Các trường bit rất khác nhau giữa các trình biên dịch, xin lỗi.

Với GCC, các máy endian lớn sắp xếp các bit lớn đầu tiên và các máy endian nhỏ đặt các bit cuối cùng trước.

K&R cho biết "Các thành viên trường [bit-] liền kề của cấu trúc được đóng gói thành các đơn vị lưu trữ phụ thuộc vào triển khai theo hướng phụ thuộc vào triển khai. Khi một trường theo sau một trường khác sẽ không phù hợp ... nó có thể bị tách giữa các đơn vị hoặc đơn vị có thể padded. Trường không tên có chiều rộng 0 buộc đệm này ... "

Do đó, nếu bạn cần bố trí nhị phân độc lập với máy, bạn phải tự làm.

Câu lệnh cuối cùng này cũng áp dụng cho các trường không phải là bit do đệm - tuy nhiên, tất cả các trình biên dịch dường như có một số cách buộc đóng gói byte của một cấu trúc, như tôi thấy bạn đã phát hiện ra cho GCC.


K&R có thực sự được coi là một tài liệu tham khảo hữu ích, vì nó đã được tiêu chuẩn hóa trước và có lẽ (tôi cho là?) Đã được thay thế trong nhiều lĩnh vực?
underscore_d

1
K&R của tôi là hậu ANSI.
Joshua

1
Bây giờ điều đó thật đáng xấu hổ: Tôi không nhận ra rằng họ đã phát hành một bản sửa đổi sau ANSI. Lỗi của tôi!
underscore_d

35

Nên tránh các trường bit - chúng không dễ di chuyển giữa các trình biên dịch ngay cả đối với cùng một nền tảng. từ tiêu chuẩn C99 6.7.2.1/10 - "Các từ chỉ định cấu trúc và liên hợp" (có từ ngữ tương tự trong tiêu chuẩn C90):

Việc triển khai có thể phân bổ bất kỳ đơn vị lưu trữ địa chỉ nào đủ lớn để chứa trường bit. Nếu vẫn còn đủ không gian, một trường bit ngay sau một trường bit khác trong cấu trúc sẽ được đóng gói thành các bit liền kề của cùng một đơn vị. Nếu không đủ dung lượng vẫn còn, liệu một trường bit không phù hợp có được đưa vào đơn vị tiếp theo hoặc chồng lên các đơn vị liền kề hay không là do việc triển khai xác định. Thứ tự phân bổ các trường bit trong một đơn vị (từ bậc cao đến bậc thấp hoặc từ bậc thấp đến bậc cao) được xác định bởi sự thực thi. Căn chỉnh của đơn vị lưu trữ địa chỉ là không xác định.

Bạn không thể đảm bảo liệu một trường bit có 'mở rộng' một ranh giới int hay không và bạn không thể xác định liệu một trường bit bắt đầu ở phần cuối thấp của int hay phần cuối của int (điều này độc lập với việc bộ xử lý có big-endian hoặc little-endian).

Ưu tiên mặt nạ bit. Sử dụng nội tuyến (hoặc thậm chí macro) để thiết lập, xóa và kiểm tra các bit.


2
Thứ tự của các trường bit có thể được xác định tại thời điểm biên dịch.
Greg A. Woods,

9
Ngoài ra, trường bit rất được ưu tiên khi xử lý cờ bit không có biểu diễn bên ngoài bên ngoài chương trình (tức là trên đĩa hoặc trong thanh ghi hoặc trong bộ nhớ được truy cập bởi các chương trình khác, v.v.).
Greg A. Woods,

1
@ GregA.Woods: Nếu thực sự là như vậy, vui lòng cung cấp câu trả lời mô tả cách thức. Tôi không thể tìm thấy bất cứ điều gì nhưng nhận xét của bạn khi googling cho nó ...
mozzbozz

1
@ GregA.Woods: Xin lỗi, lẽ ra tôi nên viết thư cho nhận xét nào mà tôi đã giới thiệu. Ý tôi là: Bạn nói rằng "Thứ tự của các trường bit có thể được xác định tại thời điểm biên dịch.". Tôi không biết gì về nó và làm thế nào để làm điều đó.
mozzbozz

2
@mozzbozz Có một cái nhìn tại planix.com/~woods/projects/wsg2000.c và tìm kiếm các định nghĩa và sử dụng của _BIT_FIELDS_LTOH_BIT_FIELDS_HTOL
Greg A. Woods

11

endianness đang nói về lệnh byte không phải lệnh bit. Ngày nay , chắc chắn 99% rằng các lệnh bit đã được sửa. Tuy nhiên, khi sử dụng các trường bit, độ bền cần được tính đến. Xem ví dụ bên dưới.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

6
Đầu ra của a và b chỉ ra rằng endianness vẫn đang nói về thứ tự bit VÀ thứ tự byte.
Lập trình viên Windows

ví dụ tuyệt vời với chút trật tự và byte đặt hàng problematics
Jonathan

1
Bạn đã thực sự biên dịch và chạy mã? Các giá trị cho "a" và "b" có vẻ không hợp lý đối với tôi: về cơ bản bạn đang nói rằng trình biên dịch sẽ hoán đổi các nibbles trong một byte vì tính endianness. Trong trường hợp "d", endiannes không được ảnh hưởng đến thứ tự byte trong mảng char (giả sử char dài 1 byte); nếu trình biên dịch làm điều đó, chúng tôi sẽ không thể lặp qua một mảng bằng cách sử dụng con trỏ. Mặt khác, nếu bạn đã sử dụng một mảng gồm hai số nguyên 16 bit, ví dụ: uint16 data [] = {0x1234,0x5678}; thì d chắc chắn sẽ là 0x7856 trong các hệ thống endian nhỏ.
Krauss

6

Hầu hết thời gian, có thể, nhưng đừng đặt cược trang trại vào nó, vì nếu bạn sai, bạn sẽ thua lớn.

Nếu bạn thực sự, thực sự cần có thông tin nhị phân giống hệt nhau, bạn sẽ cần tạo các trường bit với mặt nạ bit - ví dụ: bạn sử dụng một đoạn ngắn không dấu (16 bit) cho Message, sau đó tạo những thứ như versionMask = 0xE000 để đại diện cho ba bit trên cùng.

Có một vấn đề tương tự với căn chỉnh trong cấu trúc. Ví dụ, các CPU Sparc, PowerPC và 680x0 đều là big-endian và mặc định chung cho các trình biên dịch Sparc và PowerPC là sắp xếp các thành viên cấu trúc trên ranh giới 4 byte. Tuy nhiên, một trình biên dịch mà tôi đã sử dụng cho 680x0 chỉ được căn chỉnh trên ranh giới 2 byte - và không có tùy chọn nào để thay đổi căn chỉnh!

Vì vậy, đối với một số cấu trúc, kích thước trên Sparc và PowerPC giống hệt nhau, nhưng nhỏ hơn trên 680x0 và một số thành viên nằm trong các khoảng cách bộ nhớ khác nhau trong cấu trúc.

Đây là vấn đề với một dự án mà tôi đã làm việc, bởi vì một quy trình máy chủ chạy trên Sparc sẽ truy vấn một máy khách và phát hiện ra đó là big-endian và giả sử nó có thể phun ra các cấu trúc nhị phân trên mạng và máy khách có thể đối phó. Và điều đó hoạt động tốt trên các máy khách PowerPC và đã gặp sự cố lớn trên các máy khách 680x0. Tôi đã không viết mã và mất khá nhiều thời gian để tìm ra vấn đề. Nhưng nó rất dễ sửa chữa một khi tôi đã làm.


1

Cảm ơn @BenVoigt vì nhận xét rất hữu ích của bạn bắt đầu

Không, chúng được tạo ra để tiết kiệm bộ nhớ.

Linux nguồn không sử dụng một chút để phù hợp với lĩnh vực đến một cấu trúc bên ngoài: /usr/include/linux/ip.h có mã này cho byte đầu tiên của một IP datagram

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Tuy nhiên, theo nhận xét của bạn, tôi đang từ bỏ việc cố gắng làm cho điều này hoạt động cho trường bit nhiều byte frag_off .


-9

Tất nhiên câu trả lời tốt nhất là sử dụng một lớp đọc / ghi các trường bit như một luồng. Sử dụng cấu trúc trường bit C không được đảm bảo. Chưa kể đến việc sử dụng điều này trong thế giới thực được coi là thiếu chuyên nghiệp / lười biếng / ngu ngốc.


5
Tôi nghĩ rằng đó là sai lầm khi trạng thái đó nó là ngu ngốc để sử dụng các trường chút vì nó cung cấp một cách rất sạch sẽ để đại diện cho thanh ghi phần cứng, mà nó được tạo ra để mô hình, trong C.
trondd

13
@trondd: Không, chúng được tạo ra để tiết kiệm bộ nhớ. Trường bit không nhằm mục đích ánh xạ tới cấu trúc dữ liệu bên ngoài, chẳng hạn như thanh ghi phần cứng được ánh xạ bộ nhớ, giao thức mạng hoặc định dạng tệp. Nếu chúng được dự định để ánh xạ đến các cấu trúc dữ liệu bên ngoài, thứ tự đóng gói sẽ được chuẩn hóa.
Ben Voigt

2
Sử dụng bit giúp tiết kiệm bộ nhớ. Sử dụng trường bit làm tăng khả năng đọc. Sử dụng ít bộ nhớ hơn sẽ nhanh hơn. Sử dụng các bit cho phép các hoạt động nguyên tử phức tạp hơn. Ngoài các ứng dụng trong thế giới thực, cần có hiệu suất và các hoạt động nguyên tử phức tạp. Câu trả lời này sẽ không phù hợp với chúng tôi.
johnnycrash

@BenVoigt có thể đúng, nhưng nếu một lập trình viên sẵn sàng xác nhận rằng thứ tự của trình biên dịch / ABI của họ phù hợp với những gì họ cần và hy sinh khả năng di chuyển nhanh cho phù hợp - thì họ chắc chắn có thể hoàn thành vai trò đó. Đối với 9 *, khối lượng "lập trình viên thế giới thực" có thẩm quyền nào coi tất cả việc sử dụng trường bit là "không chuyên nghiệp / lười biếng / ngu ngốc" và họ đã nêu điều này ở đâu?
underscore_d

2
Sử dụng ít bộ nhớ hơn không phải lúc nào cũng nhanh hơn; thường sẽ hiệu quả hơn khi sử dụng nhiều bộ nhớ hơn và giảm các thao tác sau khi đọc và chế độ bộ xử lý / bộ xử lý có thể làm cho điều đó càng đúng.
Dave Newton
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.