C Language
Twierdzenie
Szukaj…
Wprowadzenie
Twierdzenie jest orzeczeniem, że przedstawiony warunek musi być spełniony w momencie, gdy oprogramowanie napotka takie stwierdzenie. Najczęściej są to proste stwierdzenia , które są sprawdzane w czasie wykonywania. Jednak twierdzenia statyczne są sprawdzane podczas kompilacji.
Składnia
- assert (wyrażenie)
- static_assert (wyrażenie, komunikat)
- _Static_assert (wyrażenie, komunikat)
Parametry
Parametr | Detale |
---|---|
wyrażenie | wyrażenie typu skalarnego. |
wiadomość | literał łańcuchowy, który ma być zawarty w komunikacie diagnostycznym. |
Uwagi
Zarówno assert
jak i static_assert
są makrami zdefiniowanymi w assert.h
.
Definicja assert
zależy od makra NDEBUG
które nie jest zdefiniowane przez bibliotekę standardową. Jeśli zdefiniowano NDEBUG
, assert
działa:
#ifdef NDEBUG # define assert(condition) ((void) 0) #else # define assert(condition) /* implementation defined */ #endif
Różni się opinia na temat tego, czy NDEBUG
powinien być zawsze używany do kompilacji produkcyjnych.
- Zwolennicy
assert
żeassert
abort
połączeń i komunikaty potwierdzające nie są pomocne dla użytkowników końcowych, więc wynik nie jest pomocny dla użytkownika. Jeśli masz fatalne warunki do sprawdzenia kodu produkcyjnego, powinieneś użyć zwykłych warunkówif/else
iexit
lubquick_exit
aby zakończyć program. W przeciwieństwie doabort
, pozwalają one program zrobić jakieś porządki (za pośrednictwem funkcji zarejestrowanychatexit
lubat_quick_exit
). - Con-camp twierdzi,
assert
wywołaniaassert
nigdy nie powinny uruchamiać się w kodzie produkcyjnym, ale jeśli tak, sprawdzany warunek oznacza, że jest coś dramatycznie złego, a program źle się zachowa, jeśli wykonanie będzie kontynuowane. Dlatego lepiej jest mieć aktywne twierdzenia w kodzie produkcyjnym, ponieważ jeśli się uruchomią, piekło już się uwolniło. - Inną opcją jest użycie domowego systemu asercji, które zawsze przeprowadzają kontrolę, ale obsługują błędy w różny sposób między rozwojem (tam, gdzie
abort
jest właściwe) a produkcją (gdzie „nieoczekiwany błąd wewnętrzny - skontaktuj się z pomocą techniczną” może być bardziej odpowiedni).
static_assert
rozwija się do _Static_assert
który jest słowem kluczowym. Warunek jest sprawdzany w czasie kompilacji, dlatego condition
musi być stałym wyrażeniem. Nie ma potrzeby, aby traktować to inaczej w zależności od rozwoju i produkcji.
Warunki wstępne i dodatkowe
Jednym z przypadków użycia dla asercji jest warunek wstępny i późniejszy. Może to być bardzo przydatne do utrzymania niezmienności i projektu na podstawie umowy . Na przykład długość jest zawsze zerowa lub dodatnia, więc ta funkcja musi zwrócić wartość zerową lub dodatnią.
#include <stdio.h>
/* Uncomment to disable `assert()` */
/* #define NDEBUG */
#include <assert.h>
int length2 (int *a, int count)
{
int i, result = 0;
/* Precondition: */
/* NULL is an invalid vector */
assert (a != NULL);
/* Number of dimensions can not be negative.*/
assert (count >= 0);
/* Calculation */
for (i = 0; i < count; ++i)
{
result = result + (a[i] * a[i]);
}
/* Postcondition: */
/* Resulting length can not be negative. */
assert (result >= 0);
return result;
}
#define COUNT 3
int main (void)
{
int a[COUNT] = {1, 2, 3};
int *b = NULL;
int r;
r = length2 (a, COUNT);
printf ("r = %i\n", r);
r = length2 (b, COUNT);
printf ("r = %i\n", r);
return 0;
}
Proste stwierdzenie
Asercja to instrukcja używana do stwierdzenia, że fakt musi być prawdziwy po osiągnięciu tego wiersza kodu. Asercje są przydatne do zapewnienia spełnienia oczekiwanych warunków. Gdy warunek przekazany do asercji jest spełniony, nie ma akcji. Zachowanie w fałszywych warunkach zależy od flag kompilatora. Gdy asercje są włączone, fałszywe dane wejściowe powodują natychmiastowe zatrzymanie programu. Gdy są wyłączone, nie są podejmowane żadne działania. Powszechną praktyką jest włączanie asercji w kompilacjach wewnętrznych i debugujących oraz wyłączanie ich w kompilacjach wersji, chociaż asercje są często włączane w wersji. (To, czy zakończenie jest lepsze czy gorsze od błędów, zależy od programu). Asercji należy używać wyłącznie do wychwytywania wewnętrznych błędów programistycznych, co zwykle oznacza, że przekazywane są złe parametry.
#include <stdio.h>
/* Uncomment to disable `assert()` */
/* #define NDEBUG */
#include <assert.h>
int main(void)
{
int x = -1;
assert(x >= 0);
printf("x = %d\n", x);
return 0;
}
Możliwe dane wyjściowe z niezdefiniowanym NDEBUG
:
a.out: main.c:9: main: Assertion `x >= 0' failed.
Możliwe dane wyjściowe ze zdefiniowanym NDEBUG
:
x = -1
Dobrą praktyką jest globalne definiowanie NDEBUG
, abyś mógł łatwo skompilować kod ze wszystkimi NDEBUG
, zarówno włączonymi jak i wyłączanymi. Łatwym sposobem na to jest zdefiniowanie NDEBUG
jako opcji kompilatora lub zdefiniowanie go we wspólnym nagłówku konfiguracji (np. config.h
).
Twierdzenie statyczne
Asercje statyczne służą do sprawdzania, czy warunek jest spełniony podczas kompilacji kodu. Jeśli tak nie jest, kompilator musi wydać komunikat o błędzie i zatrzymać proces kompilacji.
Asercja statyczna to taka, która jest sprawdzana w czasie kompilacji, a nie w czasie wykonywania. Warunek musi być ciągłym wyrażeniem, a jeśli false spowoduje błąd kompilatora. Pierwszy argument, sprawdzany warunek, musi być ciągłym wyrażeniem, a drugi ciąg literału.
W przeciwieństwie do assert, _Static_assert
jest słowem kluczowym. Makro wygody static_assert
jest zdefiniowane w <assert.h>
.
#include <assert.h>
enum {N = 5};
_Static_assert(N == 5, "N does not equal 5");
static_assert(N > 10, "N is not greater than 10"); /* compiler error */
Przed wersją C11 nie było bezpośredniego wsparcia dla twierdzeń statycznych. Jednak w C99 można było emulować twierdzenia statyczne za pomocą makr, które wyzwalałyby niepowodzenie kompilacji, gdyby warunek czasu kompilacji był fałszywy. W przeciwieństwie do _Static_assert
, drugim parametrem musi być poprawna nazwa tokena, aby można było z nim utworzyć nazwę zmiennej. Jeśli asercja się nie powiedzie, nazwa zmiennej pojawia się w błędzie kompilatora, ponieważ zmienna ta została użyta w niepoprawnej składniowej deklaracji tablicowej.
#define STATIC_MSG(msg, l) STATIC_MSG2(msg, l)
#define STATIC_MSG2(msg,l) on_line_##l##__##msg
#define STATIC_ASSERT(x, msg) extern char STATIC_MSG(msg, __LINE__) [(x)?1:-1]
enum { N = 5 };
STATIC_ASSERT(N == 5, N_must_equal_5);
STATIC_ASSERT(N > 5, N_must_be_greater_than_5); /* compile error */
W wersjach wcześniejszych niż C99 nie można było deklarować zmiennych w dowolnych lokalizacjach w bloku, dlatego trzeba bardzo ostrożnie korzystać z tego makra, upewniając się, że pojawia się tylko tam, gdzie deklaracja zmiennej byłaby ważna.
Asercja nieosiągalnego kodu
Podczas programowania, gdy pewne ścieżki kodu muszą być zabezpieczone przed zasięgiem przepływu sterowania, można użyć funkcji assert(0)
aby wskazać, że taki warunek jest błędny:
switch (color) {
case COLOR_RED:
case COLOR_GREEN:
case COLOR_BLUE:
break;
default:
assert(0);
}
Ilekroć argument assert()
wartość false, makro zapisuje informacje diagnostyczne do standardowego strumienia błędów, a następnie przerywa działanie programu. Informacje te obejmują numer pliku i linii instrukcji assert()
i mogą być bardzo pomocne w debugowaniu. Aserty można wyłączyć, definiując makro NDEBUG
.
Innym sposobem zakończenia programu w przypadku wystąpienia błędu jest exit
ze standardowej funkcji bibliotecznej, quick_exit
exit
lub abort
. exit
i quick_exit
przyjmują argument, który można przekazać z powrotem do środowiska. abort()
(i tym samym assert
) może być naprawdę poważnym zakończeniem twojego programu, a niektóre porządki, które w innym przypadku zostałyby wykonane na końcu wykonania, mogą nie zostać wykonane.
Podstawową zaletą assert()
jest to, że automatycznie drukuje informacje debugowania. Wywołanie abort()
ma tę zaletę, że nie można go wyłączyć jak aser, ale nie może powodować wyświetlania żadnych informacji debugujących. W niektórych sytuacjach jednoczesne użycie obu konstruktów może być korzystne:
if (color == COLOR_RED || color == COLOR_GREEN) {
...
} else if (color == COLOR_BLUE) {
...
} else {
assert(0), abort();
}
Gdy asserty są włączone , wywołanie assert()
spowoduje wydrukowanie informacji debugowania i zakończenie programu. Wykonanie nigdy nie osiąga wywołania abort()
. Gdy aserty są wyłączone , wywołanie assert()
nic nie robi i wywoływana jest funkcja abort()
. Zapewnia to, że program zawsze kończy działanie z powodu tego błędu; włączanie i wyłączanie zapewnia tylko efekty, niezależnie od tego, czy wydrukowane jest wyjście debugowania.
Nigdy nie należy pozostawiać takiego assert
w kodzie produkcyjnym, ponieważ informacje debugowania nie są pomocne dla użytkowników końcowych i ponieważ abort
jest na ogół zbyt surowym zakończeniem, które quick_exit
procedury czyszczące zainstalowane dla exit
lub quick_exit
uruchomienia.
Potwierdź komunikaty o błędach
Istnieje sztuczka, która może wyświetlać komunikat o błędzie wraz z asercją. Zwykle piszesz taki kod
void f(void *p)
{
assert(p != NULL);
/* more code */
}
Jeśli asercja się nie powiedzie, komunikat o błędzie będzie podobny
Asercja nie powiodła się: p! = NULL, plik main.c, wiersz 5
Możesz jednak użyć logicznego AND ( &&
) również do wyświetlenia komunikatu o błędzie
void f(void *p)
{
assert(p != NULL && "function f: p cannot be NULL");
/* more code */
}
Teraz, jeśli asercja się nie powiedzie, komunikat o błędzie przeczyta coś takiego
Asercja nie powiodła się: p! = NULL && "funkcja f: p nie może być NULL", plik main.c, wiersz 5
Powodem, dla którego to działa, jest to, że literał łańcuchowy zawsze zwraca wartość niezerową (prawda). Dodanie && 1
do wyrażenia logicznego nie ma żadnego efektu. Zatem dodanie && "error message"
również nie ma żadnego efektu, poza tym, że kompilator wyświetli całe wyrażenie, które nie powiodło się.