Tại sao cấu trúc này có kích thước 3 thay vì 2?


91

Tôi đã xác định cấu trúc này:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

Cho sizeof(col)tôi kết quả là 3, nhưng nó không phải là 2? Nếu tôi nhận xét chỉ một phần tử, thì sizeoflà 2. Tôi không hiểu tại sao: năm phần tử của 3 bit bằng 15 bit, và đó là nhỏ hơn 2 byte.

Có "kích thước nội bộ" trong việc xác định một cấu trúc như thế này không? Tôi chỉ cần làm rõ, bởi vì từ quan niệm của tôi về ngôn ngữ cho đến nay, tôi mong đợi kích thước là 2 byte, không phải 3.


4
Nó có thể là tối ưu hóa sự liên kết. Nó bắt đầu một byte mới, nếu kích thước bit tiếp theo không vừa với không gian bị chiếm dụng thực tế.
πάντα ῥεῖ

4
Trừ khi bạn có một số ràng buộc bên ngoài yêu cầu đóng gói bit và nền tảng của bạn cung cấp một số đảm bảo bổ sung so với những gì tiêu chuẩn cung cấp, sẽ có rất ít điểm khi sử dụng bitfields.
David Rodríguez - dribeas

3
Lưu ý rằng đối với C, sử dụng char ít di động hơn so với sử dụng int, stackoverflow.com/a/23987436/23118 .
hlovdal

2
Lưu ý rằng hầu hết mọi thứ về trường bit đều được định nghĩa thực thi. Bạn có thể nhận được các câu trả lời khác nhau từ các trình biên dịch khác nhau và sẽ không có quyền truy đòi. Cũng lưu ý rằng vì bạn không chỉ định signed charhoặc unsigned char, bạn không thể biết nếu không xem tài liệu trình biên dịch sẽ coi 'đơn giản' chartrong một trường bit là có dấu hay không dấu và quyết định có thể (về lý thuyết) khác với quyết định về việc 'đơn giản' charđược ký kết hoặc unsigned khi được sử dụng bên ngoài một trường bit.
Jonathan Leffler

3
Cụ thể, trong C99, §6.7.2.1 Struct và specifiers đoàn, ¶4 Một cắn trường sẽ có một loại đó là một phiên bản đủ điều kiện hoặc không đủ tiêu chuẩn của _Bool, signed int, unsigned int, hoặc một số loại thực hiện xác định khác. charDo đó, việc sử dụng rơi vào danh mục 'loại do triển khai khác xác định'.
Jonathan Leffler

Câu trả lời:


95

Bởi vì bạn đang sử dụng charlàm kiểu cơ bản cho các trường của mình, trình biên dịch cố gắng nhóm các bit theo từng byte và vì nó không thể đặt nhiều hơn tám bit trong mỗi byte, nó chỉ có thể lưu trữ hai trường trên mỗi byte.

Tổng số bit mà cấu trúc của bạn sử dụng là 15, vì vậy kích thước lý tưởng để phù hợp với nhiều dữ liệu đó sẽ là a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

Đoạn mã trên (đối với nền tảng 64-bit như của tôi) sẽ thực sự mang lại 2cấu trúc thứ hai. Đối với bất kỳ thứ gì lớn hơn a short, struct sẽ không lấp đầy nhiều hơn một phần tử của loại được sử dụng, vì vậy - đối với cùng nền tảng đó - struct sẽ kết thúc với kích thước bốn cho int, tám cho long, v.v.


1
Định nghĩa struct được đề xuất vẫn còn sai. Định nghĩa cấu trúc chính xác sẽ sử dụng "unsigned short".
user3629249

21
@ user3629249 Tại sao viết tắt không dấu là 'đúng'? Nếu người dùng muốn lưu trữ từ -4 đến 3 thì ngắn là chính xác. Nếu người dùng muốn lưu trữ từ 0 đến 7 thì không dấu là chính xác. Câu hỏi ban đầu sử dụng kiểu chữ ký nhưng tôi không thể biết đó là cố ý hay ngẫu nhiên.
Bruce Dawson

2
Tại sao có sự khác biệt giữa charshort?
GingerPlusPlus

5
@BruceDawson: Tiêu chuẩn cho phép các triển khai chưa charđược ký tên…
Thomas Eding

@ThomasEding Đúng, tiêu chuẩn cho phép ký tự không dấu. Nhưng quan điểm chính của tôi vẫn là, không có lý do nào được đưa ra để khẳng định rằng đoạn ngắn không dấu là đúng (mặc dù nó thường sẽ như vậy).
Bruce Dawson

78

Bởi vì bạn không thể có một trường gói bit kéo dài qua ranh giới căn chỉnh tối thiểu (là 1 byte) nên chúng có thể sẽ bị đóng gói như

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(Thứ tự của trường / vùng đệm bên trong cùng một byte không phải là cố ý, nó chỉ để cung cấp cho bạn ý tưởng, vì trình biên dịch có thể sắp xếp chúng theo cách nó thích)


16

Hai trường bit đầu tiên phù hợp với một trường duy nhất char. Cái thứ ba không thể phù hợp với điều đó charvà cần một cái mới. 3 + 3 + 3 = 9 không phù hợp với ký tự 8 bit.

Vì vậy, cặp đầu tiên nhận a char, cặp thứ hai nhận a char, và trường bit cuối cùng nhận một phần ba char.


15

Hầu hết các trình biên dịch đều cho phép bạn kiểm soát phần đệm, ví dụ như sử dụng #pragmas . Đây là một ví dụ với GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Lưu ý rằng hành vi mặc định của trình biên dịch là có lý do và có thể sẽ mang lại cho bạn hiệu suất tốt hơn.


9

Mặc dù tiêu chuẩn ANSI C chỉ định quá ít về cách các trường bit được đóng gói để cung cấp bất kỳ lợi thế đáng kể nào so với việc "trình biên dịch được phép đóng gói các trường bit theo cách họ thấy phù hợp", tuy nhiên trong nhiều trường hợp, nó cấm các trình biên dịch đóng gói mọi thứ theo cách hiệu quả nhất.

Đặc biệt, nếu một cấu trúc chứa trường bit, trình biên dịch được yêu cầu lưu trữ nó dưới dạng cấu trúc chứa một hoặc nhiều trường ẩn danh của một số kiểu lưu trữ "bình thường" và sau đó chia nhỏ từng trường đó thành các phần trường bit cấu thành của nó một cách hợp lý. Do đó, đã cho:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Nếu unsigned charlà 8 bit, trình biên dịch sẽ được yêu cầu cấp phát bốn trường thuộc loại đó và gán hai trường bit cho tất cả trừ một trường (sẽ nằm trong một chartrường của riêng nó). Nếu tất cả các charkhai báo đã được thay thế bằng short, thì sẽ có hai trường kiểu short, một trong số đó sẽ chứa năm trường bit và trường kia sẽ chứa hai trường còn lại.

Trên một bộ xử lý không có giới hạn căn chỉnh, dữ liệu có thể được sắp xếp hiệu quả hơn bằng cách sử dụng unsigned shortcho năm trường đầu tiên và unsigned charhai trường cuối cùng, lưu trữ bảy trường ba bit trong ba byte. Mặc dù có thể lưu trữ tám trường ba bit trong ba byte, nhưng trình biên dịch chỉ có thể cho phép điều đó nếu tồn tại kiểu số ba byte có thể được sử dụng làm kiểu "trường ngoài".

Cá nhân tôi coi các trường bit được định nghĩa là về cơ bản là vô dụng. Nếu mã cần hoạt động với dữ liệu được đóng gói nhị phân, nó phải xác định rõ ràng vị trí lưu trữ của các loại thực tế, sau đó sử dụng macro hoặc một số phương tiện tương tự khác để truy cập các bit của chúng. Sẽ rất hữu ích nếu C hỗ trợ một cú pháp như:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Cú pháp như vậy, nếu được cho phép, sẽ làm cho mã có thể sử dụng các trường bit theo kiểu di động, bất kể kích thước từ hoặc chuỗi byte (foo0 sẽ nằm trong ba bit có ý nghĩa nhỏ nhất của f1, nhưng chúng có thể được lưu trữ tại địa chỉ thấp hơn hoặc cao hơn). Tuy nhiên, vắng mặt một tính năng như vậy, macro có lẽ là cách di động duy nhất để hoạt động với những thứ như vậy.


2
Các trình biên dịch khác nhau sẽ bố trí các trường bit khác nhau. Tôi đã viết một số tài liệu về cách Visual C ++ thực hiện nó có thể có liên quan. Nó chỉ ra một số cạm bẫy khó chịu: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson

Vâng, bạn đang nói một tương đương của lưu trữ trong một loại bình thường và sử dụng toán tử trường bit để thực hiện một biến quan tâm và để đơn giản hóa cơ chế này, hãy sử dụng một số macro. Tôi nghĩ rằng mã được tạo trong c / c ++ cũng làm điều gì đó như thế này. Sử dụng một cấu trúc chỉ để tổ chức mã "tốt hơn", thực sự không cần thiết chút nào.
Raffaello
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.