C++ bez exceptionów

Korzystając z C++ na systemach embedded najczęściej wyłączamy obsługę exceptionów. W tym artykule wyjaśnię dlaczego tak robimy oraz jakie zagrożenia z tym się wiążą.

Dlaczego w embedded nie używamy exceptionów?

Systemy embedded posiadają ograniczenia rozmiaru programu, wykorzystywanej pamięci i czasu wykonywania poszczególnych operacji. Wykorzystanie wyjątków rodzi następujące problemy:

  • Czas obsługi wyjątku jest niedeterministyczny i stosunkowo długi – niektóre systemy nie mogą sobie pozwolić na długie oczekiwanie i nieznany czas operacji. Dlatego w systemach hard real time na pewno nie możemy skorzystać z exceptionów.
  • Zwiększa rozmiar programu dodając kod do obsługi exceptionów. – wynikowa binarka jest zdecydowanie większa niż analogiczny program w C. Jeżeli dysponujemy naprawdę małym mikrokontrolerem jest to poważny problem. Jednak nowe procesory mają coraz więcej FLASHu, dlatego ma to coraz mniejsze znaczenie. Problem często jest bardziej w głowach inżynierów przyzwyczajonych, aby zawsze żyłować na maksa rozmiar programu. W ten sposób także rodzą się mity o tym jak C++ nie nadaje się do embedded.

W gcc możemy użyć flagi -fno-exception aby wyłączyć obsługę wyjątków.

Mozilla Firefox – program na PC bez exceptionów

Czasemwyjątki są wyłączane rónież w programach na PC. Przyczyną jest zwykle chęć poprawienia wydajności. Ogólna zasada mówi, że nie powinniśmy tego robić. Dobrze napisany kod z wyjątkami nie spowalnia specjalnie programu. Problemy z performancem zwykle są gdzie indziej. A wyłączając exceptiony możemy sobie łatwo strzelić w stopę. Przykładem programu na PC napisanego bez obsługi wyjątków jest Mozilla Firefox. Można o tym przeczytać w ich informacji o dozwolonych konstrukcjach języka. Jak można się domyślać, zrodziło to pewne trudności, o których można przeczytać tutaj.

Na co trzeba uważać?

Czai się tu na nas pewna pułapka. To, że skompilowaliśmy nasz kod z flagą -fno-exception nie oznacza wcale, że zewnętrzne biblioteki również są skompilowane z tą flagą. Największym zagrożeniem są tutaj biblioteki standardowe. Kompilator dołącza prekompilowane wersje wykorzystywanych funkcji, które oczywiście mogą rzucać wyjątki. Teoretycznie możemy samemu skompilować bibliotekę standardową z odpowiednimi flagami, ale nie jest ona dostosowana do pracy bez exceptionów.

Co więcej takich wyjątków rzuconych przez bibliotekę standardową nie możemy złapać w naszym kodzie. Flaga -fno-exception powoduje, że użycie bloku try/catch jest traktowane jako błąd kompilacji. Czyli nie ma możliwości przechwycenia exceptiona z STLa.

Operator new bez exceptionów

Przykładowym miejscem, gdzie coś może pójść nie tak jest operator new, który w przypadku błędu alokacji zwraca wyjątek bad_alloc. Alternatywą może tu być placement new ze składnią:

MyClass *tmp = new(0xA5A5A5A5) MyClass();

Gdzie w nawiasie podajemy adres pod którym ma zostać utworzony obiekt. Kolejną opcją jest nothrow new:

MyClass *tmp = new (std::nothrow) MyClass();

W tym wypadku otrzymamy wskaźnik do zaalokowanego obiektu w przypadku sukcesu lub null pointer w przypadku błędu.

Uwagi końcowe

W systemach embedded nie zaleca się korzystania z heapa, a więc i używać operatora new. Tak samo zamiast algorytmów i kontenerów z STLa warto korzystać ze specjalnych bibliotek na embedded. Więcej na ten temat pisałem w niedawnym artykule:

Przydatne biblioteki C++ STM32

 

 

 

 

 

1 Comment

  1. Ciekawostka jest taka, że główny wspomniany w artykule problem – czyli to, że po wyłączeniu wyjątków w aplikacji, wciąż mogą być one obecne w bibliotece standardowej toolchaina – jest w sumie jedną z głównych przyczyn istnienia mojego wariantu toolchaina dla ARM, czyli bleeding-edge-toolchain.

    https://github.com/FreddieChopin/bleeding-edge-toolchain

    W przypadku bleeding-edge-toolchain biblioteki te są właśnie kompilowane z wyłączonymi wyjątkami C++, dzięki czemu problem w sumie przestaje istnieć (;

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *