2012-12-18 10 views
5

Mój kod wykorzystuje szeroko kompilatorowe warianty tak, aby oznaczać błędy w czasie kompilacji przed czasem uruchomienia, a także w celu zwiększenia wydajności, nie wykonując asserts w czasie wykonywania.Kompilator języka C stwierdza: jak dynamicznie używać ich wszędzie tam, gdzie wyrażenie jest poprawione?

#define COMPILER_ASSERT(EXPR) switch (0) {case 0: case (EXPR):;} 

Wszystko dobrze. Chciałbym rozszerzyć to, aby użyć kompilatorów dla następujących przypadków. Powiedzmy, że mam makro, które jest wywoływane ze 100 miejsc, z których 99 przekazuje ustaloną wartość, z których 1 przekazuje zmienną. W jaki sposób mogę zakodować makro, aby uczynić to kompilatorem zapewnieniem w 99 miejscach, a awesome w ostatnim.

Gdybym mógł zagwarantować, że funkcja MY_FUNCTION() była zawsze wywoływana z ustaloną wartością, mógłbym ją zakodować w ten sposób.

void my_function(int x) 
{ 
    //do something 
} 

#define MY_FUNCTION(X) \ 
    COMPILER_ASSERT(X != 0); \ 
    my_function(X) 

//These can all take advantage of a compiler assert. 
MY_FUNCTION(1); 
MY_FUNCTION(SOME_HASH_DEFINE); 
MY_FUNCTION(sizeof(SOME_STRUCTURE)); 
//This can't (this is a contrived example - actual code is complex). 
int some_variable = 1; 
MY_FUNCTION(some_variable); 

Tak więc, jeśli nie mogę zagwarantować, że X jest ustalona, ​​ale chcemy wykorzystać dla każdego wywołania My_function() gdzie to jest, jak mogę zakodować je? Coś jak:

#define MY_FUNCTION(X) \ 
    if (X is a fixed value) COMPILER_ASSERT(X != 0); \ 
    else assert(X != 0); \ 
    my_function(X) 

Przekodowywanie połączeń do MY_FUNCTION(), aby przekazywać tylko określone wartości, nie jest dla mnie opcją. Tak, mógłbym zdefiniować MY_FUNCTION_FIXED_X i MY_FUNCTION_VARIABLE_X, ale to ujawnia to wszystko do kodu wywołującego.

Dzięki za pomoc. NickB

+1

Może mógłbyś użyć C11, który ma prawdziwe kompilacje? –

+0

Kiedy mówisz "naprawiony", masz na myśli stałą czasu kompilacji? Jeśli tak, wyobrażam sobie, że wartość byłaby inna w 99 wywołaniach kodu klienta, czy to prawda? – Anon

+0

Prawidłowo. Jednym z przykładów jest sytuacja, w której wartość przekazywana do funkcji MY_FUNCTION (X) musi znajdować się w określonym zakresie liczb całkowitych, i chcę potwierdzić, że X jest w zakresie. – NickB

Odpowiedz

2

Jeśli kompilator C obsługuje tablice o zmiennej długości, można napisać coś takiego:

#define GENERIC_ASSERT(EXPR) \ 
    ((EXPR) ? (void) 0 : assert((EXPR)), (void) sizeof(char[(EXPR) ? 1 : -1])) 

Jeśli EXPR jest fałszywie ceniony czas kompilacji stała, to sprowadza się do:

(assert((EXPR)), (void) sizeof(char[-1])) 

co jest błędem kompilacji (dotyczy tablicy o ujemnej długości).

Jeśli EXPR jest prawdziwym wycenione czas kompilacji stałe, otrzymujemy:

((void) 0), (void) 1) 

Zarówno Clang i gcc są zdolne do zmniejszenia dochodzić do niczego, jeśli wywołany z prawdziwego wartościach stałej czasu kompilacji.

Jeśli EXPR ma wartość wykonania wyrażenie sizeof być uruchamiane spowodowałoby błędów wykonania (na przykład przerwanie), tak assert sekwencjonuje najpierw przez zastosowanie operatora przecinkami.

Niestety w czasie kompilacji stałą przypadku wyświetlania komunikatu o błędzie przez gcc nie jest szczególnie pouczające:

prog.c:5: error: size of array ‘type name’ is negative 

W Clang to trochę lepiej:

error: array size is negative 
    GENERIC_ASSERT(2 + 2 == 5); 
    ^~~~~~~~~~~~~~~~~~~~~~~~~~ 
note: expanded from: 
    ((EXPR) ? (void) 0 : assert((EXPR)), (void) sizeof(char[(EXPR) ? 1 : -1])) 
                  ^~~~~~~~~~~~~~~ 
+0

Nie sądzę, że tego właśnie szukałem. Szukam czegoś, co skompiluje się do kompilatora, jeśli wyrażenie jest poprawione i kompiluje się tylko do środowiska wykonawczego, jeśli wyrażenie jest zmienne, więc daje najlepszą wydajność w czasie wykonywania, eliminując z kodu wykonywalnego jakiekolwiek potwierdzenie, którego wynik jest znany podczas kompilacji czas. – NickB

+0

@NickB jeśli 'WYRAŻ' jest stałą czasową kompilacji,' assert' znajduje się w nieosiągalnej gałęzi (ponieważ 'WYRAŻE' jest prawdziwe, branża' (void) 0' jest wzięta), więc powyższe zostanie skompilowane do puste oświadczenie. – ecatmur

0

Jak ten temat:

#define MY_FUNCTION(X) \ 
    do{ \ 
if (X>=1 && X<=20) {\ 
COMPILER_ASSERT(X != 0); \ 
my_function(X);\ 
}\ // end if block 
    // you can use break here too 
    else myfunction2(X,__FILE__,__LINE__); \ // this will be the runtime assert 
}while (0); 


void myfunction2(int x, const char * file, const int line) 
{ 
// print here information 
exit(-1); 
} 
0

W P99 Mam to makro dla numerów statycznych

# if p99_has_feature(c_static_assert) 
# define static_assert _Static_assert 
# else 
# define static_assert(EXPR, DIAGSTR)       \ 
extern char const p00_compiletime_assert[       \ 
sizeof((void const*[3*(!!(EXPR)) - 1]){       \ 
    &p00_compiletime_assert,          \ 
    "static assertion failed: " P99_STRINGIFY(EXPR) ", " DIAGSTR}) \ 
] 
extern char const p00_compiletime_assert[sizeof(void const*[2])]; 
# endif 
#endif 

ma tę zaletę, że można go używać niemal wszędzie, przynajmniej z C99, gdzie można użyć deklaracji. (C99 pozwala na mieszanie instrukcji i deklaracji), więc można tego używać wewnątrz dowolnego bloku funkcyjnego lub w zakresie plików pod warunkiem, że EXPR jest stałą całkowitą czasu kompilacji.