piątek, 5 maja 2017

C - Makro assert()

Ten post chciałbym poświęcić na opisanie makra assert, które pozwalają na sprawdzanie danych jakie są podawane do funkcji, obliczeń.

Makro sprawdza czy dane wyrażenie jest różne od 0, jeśli nie, to program zostanie zatrzymany. Jest ono zdefiniowane w bibliotece assert,h, bądź można przygotować jego własną implementację.


Dane z biblioteki:

W przypadku standardowej obsługi wykorzystuje się makro zdefiniowane w wspomnianej wcześniej bibliotece.Do niego jest podawany warunek, który zostaje sprawdzony. Jeśli sprawdzenie wykaże błąd, to zostanie wyświetlona informacja o miejscu jego wystąpienia. Jest ono definiowane po każdym dołączeniu biblioteki assert.h.

Przykład, dosyć prosty, zawiera implementację takiego makra w programie:

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <stdint.h>
  4. int main()
  5. {
  6.     uint8_t zmienna=10;
  7.     assert(zmienna>0);
  8.     zmienna = 12;
  9.     assert(zmienna != 12);
  10.     return 0;
  11. }

Program zawiesi wykonywanie przy drugim wywołaniu makra. Zostanie zwrócony następujący błąd:

prog: prog.c:13: main: Assertion `zmienna != 12' failed.

Makro można też wykorzystać do sprawdzenia czy dany ciąg znaków nie przekracza jej zadanej liczby:

  1. assert(strlen(buffer) <= 30);

Taka wiadomość informuje użytkownika w jakim pliku, funkcji oraz na którym warunku wystąpił błąd w trakcie wykonywania.

Jeśli zostanie umieszczona definicja NDEBUG w programie przez dołączeniem biblioteki to wszystkie instruckje assert zostaną pominęte.

Implementacja własna:

Innym sposobem jest zaimplementowanie własnych makr w programie. Przykładowa deklaracja może wyglądać następująco:

  1. #define Assert(cond,msg) {  if(!(cond))     z_error(msg);   }

Do makra podawany jest warunek jaki ma zostać zatwierdzony oraz wiadomość jaka zostanie wypisana gdy nie zostanie on spełniony.

  1. assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
  2. void assert_failed(uint8_t* file, uint32_t line)
  3. {
  4.     printf("Wrong parameters value: file %s on line %d\r\n", file, line)
  5. }

Tutaj podobna deklaracja makra pobrana z bibliotek od firmy ST (stm32f7xx_hal_conf.h). Podobnie jak poprzednio sprawdzany jest warunek. Jeśli wystąpi błąd to wypisany zostanie plik oraz linia w jakiej został wywołany. Dane będą wysyłane przez USART. Po uruchomieniu mikrokontrolera należy zaimplementować tylko niezbędne elementy do jego pracy, po czym przed uruchomieniem istotnych funkcji włączyć transmisję szeregową. Wtedy komunikacja może polegać na debugu z zastosowaniem USARTA bądź makrami assert.

Przykładowa funkcja wykorzystająca USART może wyglądać tak:

  1. void assert_failed(uint8_t* file, uint32_t line)
  2. {
  3.     char buffer[50];
  4.     sprintf(buffer, "Wrong parameters value: file %s on line %d\r\n", file, line)
  5.     usartPrintf(buffer, CR+LF);
  6. }

Można to także wykonać w taki sposób, że po sprawdzeniu warunku wypisujemy informacje czy zostało wykonane poprawnie czy też nie, po czym następuje wejście w pętlę nieskończoną, z której mikrokontroler w przypadku deklaracji watchdoga zostaje wyciągnięty resetem.