2009-11-04 26 views
14

Trong đợt kiểm tra mã tôi đã đi qua một số mã định nghĩa một cấu trúc đơn giản như sau:C++ dữ liệu Member Alignment và mảng đóng gói

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} 

Ở những nơi khác, một mảng của các đối tượng này được định nghĩa:

foo listOfFoos[SOME_NUM]; 

Sau đó, các cấu trúc được nguyên-sao chép vào một bộ đệm:

memcpy(pBuff,listOfFoos,3*SOME_NUM); 

mã này dựa trên assumpti a) Kích thước của foo là 3, và không có padding được áp dụng, và b.) Một mảng của các đối tượng này được đóng gói không có đệm giữa chúng.

Tôi đã thử nó với GNU trên hai nền tảng (RedHat 64b, Solaris 9), và nó hoạt động trên cả hai.

Các giả định ở trên có hợp lệ không? Nếu không, trong điều kiện nào (ví dụ: thay đổi trong OS/trình biên dịch) có thể chúng không thành công?

+1

Và ai đó đã phát minh ra std: vector ... –

+0

@Matthieu: Cảm ơn bạn đã nhắc nhở chúng tôi. Tôi chắc chắn OP đã bỏ qua điều đó. – nus

Trả lời

16

Một mảng đối tượng được yêu cầu phải tiếp giáp, vì vậy không bao giờ đệm giữa các đối tượng, mặc dù đệm có thể được thêm vào cuối đối tượng (tạo ra gần như cùng một hiệu ứng).

Cho rằng bạn đang làm việc với char, các giả định có lẽ đúng hơn thường xuyên hơn không, nhưng tiêu chuẩn C++ chắc chắn không đảm bảo nó. Một trình biên dịch khác, hoặc thậm chí chỉ là một sự thay đổi trong các cờ được chuyển tới trình biên dịch hiện tại của bạn có thể dẫn đến chèn vào giữa các phần tử của cấu trúc hoặc sau phần tử cuối cùng của cấu trúc, hoặc cả hai.

+1

Nó chắc chắn sẽ không làm tôi ngạc nhiên nếu một trình biên dịch quyết định nó thích những thứ trên ranh giới bốn byte, và đặt một byte padding ở cuối. –

+0

Thật không may, hầu hết là không. – Crashworks

20

Nó chắc chắn sẽ an toàn hơn để làm:

sizeof(foo) * SOME_NUM 
+2

không chỉ an toàn hơn, nhưng rõ ràng hơn và loại bỏ một số ma thuật. +1 – rmeador

+0

Có, tôi đồng ý với điều đó. Tôi đoán tôi đã cố gắng để có được ở padding và tổ chức mảng. Cảm ơn. –

+1

điều này không tính đến đệm giữa các phần tử mảng mặc dù. – nschmidt

2

tôi sẽ đã an toàn và thay thế các con số kỳ diệu 3 với một sizeof(foo) tôi nghĩ.

Tôi đoán là mã được tối ưu hóa cho các kiến ​​trúc xử lý trong tương lai có thể sẽ giới thiệu một số hình thức đệm.

Và cố gắng theo dõi loại lỗi đó là một nỗi đau thực sự!

1

Như những người khác đã nói, sử dụng sizeof (foo) là đặt cược an toàn hơn. Một số trình biên dịch (đặc biệt là các trình biên dịch trong thế giới nhúng) sẽ thêm một tiêu đề 4 byte vào các lớp. Những người khác có thể làm thủ thuật liên kết bộ nhớ sôi nổi, tùy thuộc vào cài đặt trình biên dịch của bạn.

Đối với nền tảng chính thống, có thể bạn không sao, nhưng nó không phải là sự đảm bảo.

5

Nếu bạn sao chép mảng của bạn như thế này bạn nên sử dụng

memcpy(pBuff,listOfFoos,sizeof(listOfFoos)); 

này sẽ luôn luôn làm việc miễn là bạn phân bổ pBuff để cùng kích thước. Bằng cách này, bạn không đưa ra giả định về đệm và căn chỉnh.

Hầu hết các trình biên dịch đều căn chỉnh cấu trúc hoặc lớp với sự sắp xếp bắt buộc của loại lớn nhất được bao gồm. Trong trường hợp ký tự của bạn có nghĩa là không có sự liên kết và đệm, nhưng nếu bạn thêm một đoạn ngắn ví dụ lớp của bạn sẽ là 6 byte lớn với một byte đệm thêm vào giữa char cuối cùng và ngắn của bạn.

2

Tất cả đều đi xuống căn chỉnh bộ nhớ.Các máy 32 bit điển hình đọc hoặc ghi 4 byte bộ nhớ cho mỗi lần thử. Cấu trúc này là an toàn từ các vấn đề bởi vì nó rơi dưới 4 byte dễ dàng mà không có vấn đề đệm khó hiểu.

Bây giờ nếu cấu trúc là như vậy:

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
    unsigned int i; 
    unsigned int j; 
} 

Logic đồng nghiệp của bạn có thể sẽ dẫn đến

memcpy(pBuff,listOfFoos,11*SOME_NUM); 

(3 char của = 3 byte, 2 ints = 2 * 4 byte, vì vậy 3 + 8)

Thật không may, do việc đệm cấu trúc thực sự chiếm 12 byte. Điều này là bởi vì bạn không thể phù hợp với ba char và một int vào đó 4 byte từ, và do đó, có một byte của không gian đệm có đẩy int vào từ riêng của nó. Điều này càng ngày càng trở thành một vấn đề thì các kiểu dữ liệu càng trở nên đa dạng hơn.

4

Tôi nghĩ rằng lý do này hoạt động vì tất cả các trường trong cấu trúc là char sắp xếp một. Nếu có ít nhất một trường không căn chỉnh 1, căn chỉnh của cấu trúc/lớp sẽ không là 1 (căn chỉnh sẽ phụ thuộc vào thứ tự trường và căn chỉnh).

Hãy xem một số ví dụ:

#include <stdio.h> 
#include <stddef.h> 

typedef struct { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Foo; 
typedef struct { 
    unsigned short i; 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Bar; 
typedef struct { Foo F[5]; } F_B; 
typedef struct { Bar B[5]; } B_F; 


#define ALIGNMENT_OF(t) offsetof(struct { char x; t test; }, test) 

int main(void) { 
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo)); 
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar)); 
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B)); 
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F)); 
} 

Khi thực hiện, kết quả là:

Foo:: Size: 3; Alignment: 1 
Bar:: Size: 6; Alignment: 2 
F_B:: Size: 15; Alignment: 1 
B_F:: Size: 30; Alignment: 2 

Bạn có thể thấy rằng Bar và F_B có sự liên kết 2 để lĩnh vực của mình tôi sẽ được sắp xếp đúng cách. Bạn cũng có thể thấy rằng Kích thước của Thanh là 6 và không phải là 5. Tương tự, kích thước của B_F (5 của Bar) là 30 và không phải là 25.

Vì vậy, nếu bạn là mã khó thay vì sizeof(...), bạn sẽ gặp sự cố ở đây.

Hy vọng điều này sẽ hữu ích.

+0

trông rất tuyệt, không may cấu trúc ẩn danh bên trong cuộc gọi offsetof không biên dịch trong msvc 2010 – nus

2

Đối với các tình huống mà những thứ như thế này được sử dụng và tôi không thể tránh được, tôi cố gắng thực hiện ngắt biên dịch khi các giả định không còn giữ. Tôi sử dụng một cái gì đó như sau (hoặc Boost.StaticAssert nếu tình hình cho phép):

static_assert(sizeof(foo) <= 3); 

// Macro for "static-assert" (only usefull on compile-time constant expressions) 
#define static_assert(exp)   static_assert_II(exp, __LINE__) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_II(exp, line) static_assert_III(exp, line) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)} 
0

Vẫn có thể là một vấn đề với sizeof() khi bạn đang đi qua các dữ liệu giữa hai máy tính. Trên một trong số họ mã có thể biên dịch với padding và trong khác mà không có, trong trường hợp sizeof() sẽ cho kết quả khác nhau. Nếu dữ liệu mảng được truyền từ một máy tính sang một máy tính khác, nó sẽ bị hiểu sai vì các phần tử mảng sẽ không được tìm thấy ở nơi được mong đợi. Một giải pháp là đảm bảo rằng gói #pragma (1) được sử dụng bất cứ khi nào có thể, nhưng điều đó có thể không đủ cho các mảng. Tốt nhất là để thấy trước vấn đề và sử dụng padding thành bội số của 8 byte cho mỗi phần tử mảng.

Các vấn đề liên quan