2009-04-30 38 views
31

Tôi muốn triển khai "khẳng định" ngăn chặn việc biên dịch, thay vì thất bại trong thời gian chạy, trong trường hợp lỗi.Trình biên dịch C xác nhận - cách triển khai?

Tôi hiện có một định nghĩa như thế này, hoạt động tốt, nhưng làm tăng kích thước của các tệp nhị phân.

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} 

Mã mẫu (không biên dịch).

#define DEFINE_A 1 
#define DEFINE_B 1 
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B); 

Tôi làm cách nào để nó không tạo ra bất kỳ mã nào (để giảm thiểu kích thước của tệp nhị phân được tạo)?

+0

Tôi thực sự không nghĩ rằng nó có thể tạo ra một khẳng định tĩnh trong đồng bằng C, rất thích biết mặc dù! –

+0

Trùng lặp với một số câu trả lời hay: http://stackoverflow.com/questions/174356/ways-to-assert-expressions-at-build-time-in-c –

+3

Vì câu hỏi này tương đối cũ: '_Static_assert' và liên kết của nó macro 'static_assert' được chuẩn hóa như của C11. Điều này hiện được tích hợp sẵn cho ngôn ngữ. – Leushenko

Trả lời

35

Xác nhận thời gian biên dịch trong tiêu chuẩn tinh khiết C là có thể, và một chút thủ thuật tiền xử lý làm cho việc sử dụng của nó trông sạch như sử dụng thời gian chạy là assert().

Bí quyết chính là tìm một cấu trúc có thể được đánh giá tại thời gian biên dịch và có thể gây ra lỗi cho một số giá trị. Một câu trả lời là việc khai báo một mảng không thể có kích thước âm. Sử dụng typedef ngăn không cho phân bổ không gian thành công và bảo toàn lỗi khi lỗi.

Bản thân thông báo lỗi sẽ tham chiếu mật mã đến khai báo kích thước âm (GCC nói "kích thước mảng foo là âm"), vì vậy bạn nên chọn tên cho loại mảng gợi ý rằng lỗi này thực sự là một kiểm tra xác nhận .

Một vấn đề nữa cần giải quyết là chỉ có thể typedef tên loại cụ thể một lần trong bất kỳ đơn vị biên dịch nào. Vì vậy, macro phải sắp xếp cho từng cách sử dụng để có được một tên kiểu duy nhất để khai báo.

Giải pháp thông thường của tôi là yêu cầu macro phải có hai tham số. Đầu tiên là điều kiện để khẳng định là đúng, và thứ hai là một phần của tên kiểu được khai báo đằng sau hậu trường. Câu trả lời bằng gợi ý bằng cách sử dụng dán mã thông báo và __LINE__ macro được xác định trước để tạo thành một tên duy nhất có thể mà không cần thêm đối số. Tuy nhiên, nếu kiểm tra xác nhận nằm trong một tệp được bao gồm, nó vẫn có thể va chạm với một séc ở cùng một số dòng trong tệp được bao gồm thứ hai hoặc tại số dòng đó trong tệp nguồn chính. Chúng ta có thể viết qua đó bằng cách sử dụng macro __FILE__, nhưng nó được định nghĩa là một hằng số chuỗi và không có thủ thuật tiền xử lý có thể biến một hằng số chuỗi trở lại thành một phần của tên định danh; chưa kể rằng tên tệp hợp pháp có thể chứa các ký tự không phải là phần hợp pháp của số nhận dạng.

Vì vậy, tôi sẽ đề nghị đoạn mã sau:

/** A compile time assertion check. 
* 
* Validate at compile time that the predicate is true without 
* generating code. This can be used at any point in a source file 
* where typedef is legal. 
* 
* On success, compilation proceeds normally. 
* 
* On failure, attempts to typedef an array type of negative size. The 
* offending line will look like 
*  typedef assertion_failed_file_h_42[-1] 
* where file is the content of the second parameter which should 
* typically be related in some obvious way to the containing file 
* name, 42 is the line number in the file on which the assertion 
* appears, and -1 is the result of a calculation based on the 
* predicate failing. 
* 
* \param predicate The predicate to test. It must evaluate to 
* something that can be coerced to a normal C boolean. 
* 
* \param file A sequence of legal identifier characters that should 
* uniquely identify the source file in which this condition appears. 
*/ 
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file) 

#define _impl_PASTE(a,b) a##b 
#define _impl_CASSERT_LINE(predicate, line, file) \ 
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1]; 

Cách sử dụng điển hình có thể là một cái gì đó như:

#include "CAssert.h" 
... 
struct foo { 
    ... /* 76 bytes of members */ 
}; 
CASSERT(sizeof(struct foo) == 76, demo_c); 

Trong GCC, một thất bại khẳng định sẽ trông như thế:

 
$ gcc -c demo.c 
demo.c:32: error: size of array `assertion_failed_demo_c_32' is negative 
$ 
+1

Nếu bạn không quan tâm đến tính di động, trong GCC \ _ \ _ COUNTER \ _ \ _ có thể được sử dụng để cung cấp cho một định danh duy nhất để dán vào tên typedef. Nó đã được thêm khá gần đây (4.3) –

+0

Tôi đã ngạc nhiên rằng tiền xử lý C chưa bao giờ có một cái gì đó như __COUNTER__. Các bộ ghép macro có các cấu trúc tương tự miễn là có các bộ tạo macro, chưa kể khả năng cho một macro để định nghĩa một macro khác có thể được sử dụng để xây dựng bất kỳ loại ký hiệu duy nhất nào có thể cần. Thật không may cho tôi, các dự án hệ thống nhúng của tôi thường bị mắc kẹt với gcc 3.4.5 hay như vậy, nếu không phải là một số trình biên dịch độc quyền (chủ yếu) tuân thủ C89. – RBerteig

+0

@RBerteig Xin chào, tôi muốn sử dụng mã này cho một dự án Nguồn mở. Bạn sẽ cấp phép gì cho nó? –

2

Khi bạn biên dịch các tệp nhị phân cuối cùng, hãy xác định MY_COMPILER_ASSERT để trống, sao cho đầu ra của nó không được bao gồm trong kết quả. Chỉ xác định nó theo cách bạn có để gỡ lỗi.

Nhưng thực sự, bạn sẽ không thể nắm bắt mọi khẳng định theo cách này. Một số chỉ không có ý nghĩa tại thời gian biên dịch (như xác nhận rằng một giá trị không phải là null). Tất cả những gì bạn có thể làm là xác minh các giá trị của #defines khác. Tôi không thực sự chắc chắn tại sao bạn muốn làm điều đó.

+4

Điều này rất hữu ích vì bất kỳ thứ gì có thể được xác minh tại thời gian biên dịch là một thứ không yêu cầu trường hợp thử nghiệm trong thời gian chạy. Khi xây dựng một triển khai giao thức di động, việc xác thực các giả định về kích thước và bố cục của cấu trúc là hữu ích. Vì kích thước và offset được xác định tại thời gian biên dịch nên việc kiểm tra chúng được ưu tiên. Ngoài ra, việc thực hiện một xác nhận thời gian biên dịch mà không cần tạo mã có nghĩa là không có lý do gì để lấy nó ra khỏi các bản xây dựng phát hành. Tại tồi tệ nhất nó cắt bảng biểu tượng với tên kiểu mồ côi. – RBerteig

3

Nếu trình biên dịch của bạn đặt vĩ mô tiền xử lý như DEBUG hoặc NDEBUG bạn có thể làm một cái gì đó như thế này (nếu không bạn có thể thiết lập này lên trong một Makefile):

#ifdef DEBUG 
#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} 
#else 
#define MY_COMPILER_ASSERT(EXPRESSION) 
#endif 

Sau đó, trình biên dịch của bạn khẳng định chỉ cho debug xây dựng.

1

Sử dụng '#error' là định nghĩa tiền xử lý hợp lệ làm cho trình biên dịch dừng trên hầu hết các trình biên dịch. Bạn chỉ có thể làm điều đó như thế này, ví dụ, để ngăn chặn biên soạn trong debug:


#ifdef DEBUG 
#error Please don't compile now 
#endif 
+4

Thật không may điều này hủy bỏ ở mức tiền xử lý, do đó, nó không thể xử lý những thứ như 'khẳng định (sizeof (dài) == sizeof (void *))'. – ephemient

4

Các writeup tốt nhất mà tôi có thể tìm thấy trên khẳng định tĩnh trong C là tại pixelbeat. Lưu ý rằng các xác nhận tĩnh đang được thêm vào C++ 0X, và có thể làm cho nó vào C1X, nhưng điều đó sẽ không diễn ra trong một thời gian. Tôi không biết nếu các macro trong liên kết tôi đưa ra sẽ tăng kích thước của các tệp nhị phân của bạn. Tôi sẽ nghi ngờ họ sẽ không, ít nhất là nếu bạn biên dịch ở mức tối ưu hợp lý, nhưng số dặm của bạn có thể thay đổi.

-1

Vâng, bạn có thể sử dụng static asserts in the boost library.

Điều tôi tin rằng họ làm ở đó, là xác định một mảng.

#define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)]; 

Nếu EXPRESSION là true, nó xác định char x[1];, là OK. Nếu sai, nó định nghĩa char x[0]; là bất hợp pháp.

+0

Nhưng trong C89 mà sẽ phá vỡ biên dịch không có vấn đề gì kể từ khi các biến chỉ có thể được khai báo ở đầu phạm vi. Có lẽ gói nó trong dấu ngoặc nhọn có thể sửa chữa nó? Ngoài ra, các mảng có kích cỡ bằng không chỉ bị cấm trong ISO C mà tôi nghĩ. Bạn cũng sẽ nhận được một loạt các cảnh báo về nhiều khai báo và các biến không sử dụng nếu không nằm trong phạm vi riêng của nó. – dreamlax

+1

'#define MY_COMPILER_ASSERT (EXPRESSION) làm {char x [(EXPRESSION)? 1: -1];} trong khi (0)' sẽ tốt hơn: bên trong niềng răng, vì vậy khai báo là hợp pháp và phạm vi, kích cỡ 1 và -1 rõ ràng hợp lệ/không hợp lệ trên tất cả các phiên bản C và buộc bạn vào 'MY_COMPILER_ASSERT (...);' với một dấu chấm phẩy sau, cho tính nhất quán trực quan với tất cả các chức năng khác giống như trong C. – ephemient

+0

Khai báo typedef là thích hợp hơn để delaring biến không sử dụng . Một typedef không sử dụng là vô hại, nhưng một biến không sử dụng có thể tự tạo ra một cảnh báo trên nhiều trình biên dịch. –

3

Tôi biết bạn quan tâm đến C, nhưng hãy xem C++ static_assert của boost. (Ngẫu nhiên, điều này có thể trở thành có sẵn trong C++ 1x.)

Chúng tôi đã làm điều gì đó tương tự, một lần nữa cho C++:

 
#define COMPILER_ASSERT(expr) enum { ARG_JOIN(CompilerAssertAtLine, __LINE__) = sizeof(char[(expr) ? +1 : -1]) } 

này chỉ hoạt động trong C++, rõ ràng. This article thảo luận cách sửa đổi để sử dụng trong C.

7

Macro sau đây hoạt động khá tốt.

 
// combine arguments (after expanding arguments) 
#define GLUE(a,b) __GLUE(a,b) 
#define __GLUE(a,b) a ## b 

#define CVERIFY(expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr) ? (+1) : (-1)] 

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__) 

Nó hoạt động cho cả C và C++ và có thể được sử dụng ở bất kỳ nơi nào typedef được cho phép. Nếu biểu thức là đúng, nó tạo ra một typedef cho một mảng 1 char (vô hại). Nếu biểu thức là sai, nó tạo ra một typedef cho một mảng -1 ký tự, mà thường sẽ dẫn đến một thông báo lỗi. Các biểu thức được đưa ra như là một arugment có thể là bất cứ điều gì mà đánh giá đến một hằng số biên dịch-thời gian (vì vậy các biểu thức liên quan đến sizeof() làm việc tốt). Điều này làm cho nó linh hoạt hơn nhiều so với

 
#if (expr) 
#error 
#endif 

nơi bạn bị giới hạn đối với các biểu thức có thể được đánh giá bởi bộ tiền xử lý.

2

Như Leander đã nói, các xác nhận tĩnh đang được thêm vào C++ 11, và bây giờ chúng có.

static_assert(exp, message)

Ví dụ

#include "myfile.hpp" 

static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!") 

void doStuff(MyClass object) { } 

Xem cppreference page trên đó.

+2

Câu hỏi đặt ra là C không phải C++. –

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