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
$
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ù! –
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 –
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