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 że assert 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ów if/else i exit lub quick_exit aby zakończyć program. W przeciwieństwie do abort , pozwalają one program zrobić jakieś porządki (za pośrednictwem funkcji zarejestrowanych atexit lub at_quick_exit ).
  • Con-camp twierdzi, assert wywołania assert 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

C11

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 */
C99

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ę.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow