2008-11-17 15 views
48

Tôi đang làm việc để tái cấu trúc một số mã cũ và đã tìm thấy vài cấu trúc có chứa các mảng có độ dài bằng không (bên dưới). Cảnh báo bị trầm cảm bởi pragma, tất nhiên, nhưng tôi đã thất bại trong việc tạo ra bởi các cấu trúc "mới" có cấu trúc như vậy (lỗi 2233). Mảng 'byData' được sử dụng làm con trỏ, nhưng tại sao không sử dụng con trỏ thay thế? hoặc mảng chiều dài 1? Và tất nhiên, không có bình luận nào được thêm vào để giúp tôi tận hưởng quy trình ... Bất kỳ nguyên nhân nào để sử dụng điều đó? Bất kỳ lời khuyên trong refactoring những người?Mảng có độ dài bằng không

struct someData 
{ 
    int nData; 
    BYTE byData[0]; 
} 

NB Đó là C++, Windows XP, VS 2003

+3

Đây là "cấu trúc hack", được mô tả trong câu hỏi 2.6 của [comp.lang.c FAQ] (http://www.c-faq.com/). Dennis Ritchie gọi nó là "chumminess không được bảo đảm với việc thực hiện C". C99 giới thiệu một tính năng ngôn ngữ mới, "linh hoạt mảng thành viên", để thay thế cấu trúc hack. Ngay cả trình biên dịch của Microsoft, được chú ý vì thiếu sự hỗ trợ C99, hỗ trợ các thành viên mảng linh hoạt. –

+0

KHÔNG thêm thẻ 'c' vào câu hỏi này.Các quy tắc C++ cho điều này khá khác với các quy tắc C. –

+0

@BenVoigt Câu trả lời được chấp nhận là mã C thuần túy, vì vậy tôi đoán chỉnh sửa của bạn sai. c hack áp dụng cho cả c và C++ theo cùng một cách –

Trả lời

33

Có đây là C-Hack.
Để tạo một mảng của bất kỳ chiều dài:

struct someData* mallocSomeData(int size) 
{ 
    struct someData* result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE)); 
    if (result) 
    { result->nData = size; 
    } 
    return result; 
} 

Bây giờ bạn có một đối tượng của someData với một mảng có chiều dài xác định.

+0

Điều này không nên ít nhất sử dụng 'new []', đây là về C++? – unwind

+1

@unwind: Không thể sử dụng mới cho mục đích này. Toàn bộ vấn đề là đây là một C-Hack và không được yêu cầu trong C++ (bởi vì chúng ta có cách làm tốt hơn). Ngoài ra tôi khá chắc chắn rằng không có mảng chiều dài là bất hợp pháp trong C + + (cũng ít nhất là C + + 03, không chắc chắn nếu điều đó đã được cập nhật trong C++ 11). –

+0

Biệt ngữ phổ biến cho điều này là "Struct Hack". –

22

Đây là một C cũ hack để cho phép một mảng có kích thước linh hoạt.

Trong tiêu chuẩn C99, điều này không cần thiết vì nó hỗ trợ cú pháp arr [].

+3

Đáng buồn thay, Visual Studio là rất nghèo khi nói đến hỗ trợ C99. :( –

+5

Nếu không giải quyết sự thật chung của nhận xét của bạn, ... trình biên dịch MS VC v9 hỗ trợ cú pháp arr [] – Cheeso

23

Thật không may, một số lý do tại sao bạn sẽ khai báo một mảng có độ dài bằng không ở cuối cấu trúc. Về cơ bản nó cung cấp cho bạn khả năng có một cấu trúc chiều dài biến đổi được trả về từ một API.

Raymond Chen đã đăng một bài đăng blog tuyệt vời về chủ đề này. Tôi đề nghị bạn hãy xem bài viết này vì nó có khả năng chứa câu trả lời bạn muốn.

Lưu ý trong bài đăng của mình, nó đề cập đến mảng có kích thước 1 thay vì 0. Đây là trường hợp vì mảng chiều dài bằng không là mục nhập gần đây hơn vào tiêu chuẩn. Bài đăng của anh ấy vẫn nên áp dụng cho vấn đề của bạn.

http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx

EDIT

Lưu ý: Mặc dù bài Raymond cho biết 0 mảng chiều dài là hợp pháp ở C99 họ đang có trong thực tế vẫn không hợp pháp ở C99. Thay vì mảng độ dài 0 ở đây, bạn nên sử dụng độ dài 1 mảng

+1

"* Đây là trường hợp vì mảng chiều dài bằng không là mục nhập gần đây hơn vào tiêu chuẩn. *" C++ 11 vẫn không cho phép các mảng 0 chiều dài (§8.3.4/1), cũng như C99 (§6.7.5.2/1) – ildjarn

+0

@ildjarn tôi đã cơ bản phân tích những gì Raymond nói ở cuối blog của mình Tôi đã không biết rằng 0 mảng chiều dài vẫn còn bất hợp pháp trong C99 cho đến khi một cuộc thảo luận gần đây bình luận với bạn về một câu hỏi khác.Tôi sẽ cập nhật câu trả lời – JaredPar

+0

Xin lỗi để nitpick câu trả lời cũ như vậy.: -PI chỉ hỏi vì một câu hỏi khác được liên kết ở đây là "bằng chứng" rằng các mảng 0 độ dài là hợp pháp C++.: -] – ildjarn

8

Thận trọng của bạn về "tại sao không sử dụng một mảng có kích thước 1" được phát hiện.

Mã đang thực hiện "C struct hack" sai, vì khai báo các mảng có độ dài bằng không là một vi phạm ràng buộc. Điều này có nghĩa là một trình biên dịch có thể từ chối quyền tấn công của bạn ngay lập tức trên con dơi tại thời gian biên dịch với một thông báo chẩn đoán ngừng dịch.

Nếu chúng tôi muốn tiến hành hack, chúng tôi phải lén qua trình biên dịch.

Cách đúng để làm "C struct hack" (đó là tương thích với C tiếng địa phương sẽ trở lại đến 1989 ANSI C, và có lẽ sớm hơn nhiều) là sử dụng một mảng hoàn toàn hợp lệ của kích thước 1:

struct someData 
{ 
    int nData; 
    unsigned char byData[1]; 
} 

Hơn nữa, thay vì sizeof struct someData, kích thước của phần trước byData được tính bằng:

offsetof(struct someData, byData); 

để phân bổ một struct someData với không gian cho 42 byte trong byData, sau đó chúng ta sẽ sử dụng:

struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42); 

Lưu ý rằng phép tính offsetof này là sự tính toán chính xác ngay cả trong trường hợp kích thước mảng bằng 0. Bạn thấy, sizeof toàn bộ cấu trúc có thể bao gồm đệm. Ví dụ, nếu chúng ta có một cái gì đó như thế này:

struct hack { 
    unsigned long ul; 
    char c; 
    char foo[0]; /* assuming our compiler accepts this nonsense */ 
}; 

Kích thước của struct hack là hoàn toàn có thể đệm cho sự liên kết vì sự ul thành viên. Nếu unsigned long rộng bốn byte, thì có thể là sizeof (struct hack) là 8, trong khi offsetof(struct hack, foo) gần như chắc chắn 5. Phương pháp offsetof là cách để có được kích thước chính xác của phần trước của cấu trúc ngay trước mảng.

Vì vậy, đó sẽ là cách để cấu trúc lại mã: làm cho nó phù hợp với cấu trúc hack cổ điển, dễ di động.

Tại sao không sử dụng con trỏ? Bởi vì một con trỏ chiếm thêm không gian và phải được khởi tạo.

Có nhiều lý do chính đáng khác không sử dụng con trỏ, cụ thể là con trỏ yêu cầu không gian địa chỉ để có ý nghĩa. Cấu trúc hack là ngoại lệ: có nghĩa là, có những tình huống trong đó bố trí như vậy phù hợp với lưu trữ bên ngoài như các vùng của tệp, gói hoặc bộ nhớ dùng chung, trong đó bạn không muốn con trỏ vì chúng không có ý nghĩa.

Cách đây vài năm, tôi đã sử dụng cấu trúc hack trong thông báo bộ nhớ chia sẻ truyền giao diện giữa nhân và không gian người dùng. Tôi không muốn có con trỏ ở đó, bởi vì chúng sẽ chỉ có ý nghĩa với không gian địa chỉ gốc của tiến trình tạo ra một thông điệp. Phần hạt nhân của phần mềm có một cái nhìn vào bộ nhớ bằng cách sử dụng ánh xạ của chính nó ở một địa chỉ khác, và vì vậy mọi thứ đều dựa trên các phép tính bù trừ.

+0

"tương thích với phương ngữ C quay lại năm 1989" - truy cập qua phần tử đầu tiên của mảng gây ra hành vi không xác định ngay cả trong C89. Cấu trúc hack dựa trên trình biên dịch "xác định" hành vi này cho chính nó. –

0

Điều đáng để chỉ ra IMO là cách tốt nhất để thực hiện tính toán kích thước, được sử dụng trong bài viết của Raymond Chen được liên kết ở trên.

struct foo 
{ 
    size_t count; 
    int data[1]; 
} 

size_t foo_size_from_count(size_t count) 
{ 
    return offsetof(foo, data[count]); 
} 

Khoản chênh lệch của mục nhập đầu tiên vào cuối phân bổ mong muốn cũng là kích thước của phân bổ mong muốn. IMO đó là một cách cực kỳ thanh lịch để thực hiện tính toán kích thước. Không quan trọng loại phần tử của mảng kích thước biến là gì. Offsetof (hoặc FIELD_OFFSET hoặc UFIELD_OFFSET trong Windows) luôn được viết theo cùng một cách. Không có biểu thức sizeof() vô tình làm rối tung lên.