Możliwe jest wykonanie kompilacji w czystym standardzie C, a odrobina preprocesora sprawia, że jego użycie wygląda tak samo czysto, jak użycie środowiska wykonawczego assert()
.
Kluczową sztuczką jest znalezienie konstruktu, który może być oceniany w czasie kompilacji i może powodować błąd w przypadku niektórych wartości. Jedną z odpowiedzi jest deklaracja tablicy, która nie może mieć ujemnego rozmiaru. Użycie parametru typedef zapobiega przydzieleniu miejsca na sukces i zapobiega błędom w przypadku niepowodzenia.
Sam komunikat o błędzie kryptycznie odwoła się do deklaracji o rozmiarze ujemnym (GCC mówi "rozmiar tablicy foo jest ujemny"), więc powinieneś wybrać nazwę typu tablicy, która podpowiada, że ten błąd naprawdę jest sprawdzaniem asercji .
Kolejną kwestią do rozwiązania jest to, że możliwe jest tylko jednoznaczne wpisanie nazwy pewnego typu w dowolnej jednostce kompilacji. Tak więc makro musi zorganizować każde użycie, aby uzyskać unikalną nazwę do deklaracji.
Moim zwykłym rozwiązaniem było wymaganie, aby makro miało dwa parametry. Pierwszym z nich jest warunek potwierdzenia, a drugi jest częścią nazwy typu zadeklarowanej za kulisami. Odpowiedź cokołu sugeruje użycie wklejonego tokena i predefiniowanego makra, aby utworzyć niepowtarzalną nazwę, bez konieczności dodatkowego argumentu.
Niestety, jeśli kontrola asercji znajduje się w dołączonym pliku, nadal może kolidować z zaznaczeniem na tym samym numerze linii w drugim dołączonym pliku lub na tym numerze linii w głównym pliku źródłowym. Moglibyśmy napisać o tym za pomocą makra __FILE__
, ale jest on zdefiniowany jako stała łańcuchowa i nie istnieje preprocesorowa sztuczka, która może zamienić ciąg z powrotem na część nazwy identyfikatora; nie wspominając, że legalne nazwy plików mogą zawierać znaki, które nie są legalnymi częściami identyfikatora.
Tak, chciałbym zaproponować następujący fragment kodu:
/** 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];
Typowe wykorzystanie może być coś takiego:
#include "CAssert.h"
...
struct foo {
... /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);
W GCC, awaria twierdzenie wyglądałby następująco:
$ gcc -c demo.c
demo.c:32: error: size of array `assertion_failed_demo_c_32' is negative
$
Naprawdę nie sądzę, że możliwe jest utworzenie statycznego dowodu w zwykłym C, ale chcielibyśmy wiedzieć! –
Powiel z kilkoma dobrymi odpowiedziami: http://stackoverflow.com/questions/174356/ways-to-assert-expressions-at-build-time-in-c –
Ponieważ to pytanie jest stosunkowo stare: '_Static_assert' i powiązane z nim makro 'static_assert' jest standaryzowane jak w C11. To jest teraz wbudowane w język. – Leushenko