Więcej o firmie ELESOFTROM

Firma ELESOFTROM specjalizuje się w wykonywaniu i naprawianiu oprogramowania dla sterowników mikroprocesorowych.

Posiadamy ponad 10-letnie doświadczenie w tej dziedzinie.

W realizowanych projektach stawiamy na niezawodność i wydajność programów.

Naprawa oprogramowania

Oprogramowanie na zamówienie


Strona główna

Pełna oferta

Doświadczenie

O firmie

Kontakt


DioneOS - RTOS dla urządzeń embedded

Firma ELESOFTROM opracowała system RTOS dla procesorów ARM Cortex-M3 oraz msp430.

System jest zoptymalizowany pod względem wydajności, dzięki czemu uzyskano krótki czas przełączania pomiędzy wątkami.
System dostarcza elementy (np. semafory, mutexy, timery, kolejki itp.) niezbędne do tworzenia wielowątkowego firmware'u.

Wersja dla Cortexa została całkowicie przetestowana przy pomocy środowiska do automatycznego testowania.

Przeczytaj więcej:

Strona o systemie DioneOS

Niezawodność

Wydajność

Dokumentacja DioneOSa

Prezentacje n.t. DioneOS


^ Blog index    << Jak zbudować projekt C++ Buildera pod Eclipse (GNU make)    >> Preprocesor C - szczegóły

Preprocessor C - podstawy (preprocesor GNU)

2014-12-16    dr inż. Piotr Romaniuk

Spis treści

Działanie preprocesora
Makrodefinicje (makra)
Argumenty makr
Zmnienna liczba argumentów
Zamiana na ciąg tekstowy (stringifikacja)
Łączenie (konkatenacja)
Predefiniowane symbole
Preprocesor - szczegóły (osobny wpis)
Preprocesor - zaawansowane wykorzystanie (osobny wpis)

Działanie preprocesora

Preprocesor jest uruchamiany podczas budowania plików w języku C/C++ przed rozpoczęciem kompilacji. Zajmuje się on:

  • włączaniem plików nagłówkowych (include)
  • warunkowym włączaniem i wyłączaniem fragmentów kodu (ifdef etc.)
  • pomijaniem komentarzy /**/, //
  • przetwarzaniem makrodefinicje

Trzeba pamiętać, że nie rozpoznaje on słów kluczowych języków (C/C++), bo są one kompilowane w następnym etapie. Ponadto całe przetwarzanie odbywa się na zasadzie przetwarzania tekstowego. Ma też ograniczone możliwości przetwarzania wyrażeń, które generalnie sprowadzają się do porównywania wartośći całkowitych.

Makrodefinicje (makra)

Bez wątpienia najciekawszym i dającym najwięcej możliwości elementem preprocesora są makrodefinicje. Pozwalają one zastępować fragmenty tekstu przez inną treść tekstową. Samo przetwarzanie makrodefinicji będzie omówione bardziej szczegółowo w osobnym opisie .
To, że są one przetwarzane i rozwijane w czasie budowania programu (a nie jego działania) może oznaczać dużą oszczędność, gdyż kod jest odpowiednio przekształcany wcześniej i przygotowany do wykonania. Oczywiście muszą to być fragmenty które są już znane podczas budowania.
Mimo pozornej prostoty, preprocesor daje duże możliwości uproszczenia pisanego kodu. Może wyręczyć programistę od wielokrotnego pisania tego samego (co stwarza niebezpieczeństwo pomyłki) a nawet pozwala na stworzenie metajęzyka, który rozszerza możliwości języka lub ukrywa szczegóły implementacji.
Makra pozwalają użyć ich jako mechanizmu uszczegółowienia (specyfikacji). Można skonstruować ogólną bibliotekę, bez znajomości funkcji niskiego poziomu, które są dostarczane dopiero na etapie wykorzystania do konkretnego celu. Przykładowo, biblioteka może używać mutexy, ale dopiero programista-użytkownik dostarczy ich definicję.

Argumenty makr

Sa dwa rodzaje makr z argumentami lub bez nich:

  • przypominające symbole,
  • wyglądające jak funkcje.
#define MY_TABLE_SIZE 256

#define SATURATE(x) (x>255)? 255 : (x)

Uwaga na brak spacji pomiędzy nazwą makra a nawiasem! Wstawienie spacji powoduje, że makro jest identyfikowane przez preprocesor jako bezargumentowe a nawias i jego zawartość staje się częścią rozwinięcia makra:

//błędna definicja z powodu spacji po nazwie:
#define SATURATE (x)   (x>255)? 255 : (x)

//wykorzystanie:
y = SATURATE(w);

//rozwinie się do:
y = (x)  (x>255)? 255 : (x)(w);

//zamiast do oczekiwanego:
y = (w>255)? 255 : (w);

Zmienna liczba argumentów

W przypadku makra z argumentami, podczas jego wykorzystania, należy podać tyle argumentów ile zostało określonych w definicji. Niezgodnośc prowadzi do błędu.

#define MACRO(x,y) (x)+(y)

w = MACRO(1);
//prowadzi do błędu:
// error: macro "MACRO" requires 2 arguments, but only 1 given

w = MACRO(1,2,3);
// error: macro "MACRO" passed 3 arguments, but takes only 2

To oczywiście zaleta, że preprocesor pilnuje zgodności liczby argumentów. Czasem jednak przydatne jest posiadanie nieokreślonej (zmiennej) liczby argumentów. Do tego służy poniższa konstrukcja:

#define DBG_MSG(level,format_string,...) printf( "<%d>" format_string, level,__VA_ARGS__ )

Jak widać, zmienną liczbę argumentów oznacza się wielokropkiem. W miejscu gdzie potrzeba je wykorzystać używa się symbolu __VA_ARGS__. Po kilku próbach wykorzystania można natknąc się na problem, gdy format_string jest ostatnim argumentem. W powyższej definicji próba takiego użycia kończy się błędem:

error: expected expression before ')' token
 #define DBG_MSG(level,format_string,...) printf( "<%d>" format_string, level,__VA_ARGS__ )
                                                                                          ^
  note: in expansion of macro DBG_MSG
  DBG_MSG(1, "some_string")
  ^

Tu z pomocą przychodzi operator konkatenacji (łączenia) w postaci dwóch znaków hash. Zostanie on omówiony poniżej, tutaj jego działanie nalezy rozumieć jako połączenie __VA_ARGS__ z przecinkiem - gdy __VA_ARGS__ jest puste przecinek jest usuwany.

#define DBG_MSG(level,format_string,...) printf( "<%d>" format_string, level, ## __VA_ARGS__ )

Zamiana na ciąg tekstowy (stringifikacja)

Preprocesor dostarcza możliwość zamiany argumentu makra na tekst. Przypuśćmy, że potrzebna jest tablica zawierająca kody błędów oraz ich nazwy, które będą wykorzystywane do wyświetlania komunikatów błędów:

#define ERRCODE_DESC( x ) { x, #x }

typedef struct errtab_entry{
	int code;
	const char* name;
	} errtab_entry_t;

#define MYERROR1 1000
#define MYERROR2 2000

errtab_entry_t errtab[]={
	ERRCODE_DESC( MYERROR1 ),
	ERRCODE_DESC( MYERROR2 ),
	...
	};
//powyzszy kod utworzy tabelę:
errtab_entry_t errtab[]={
	{ 1000, "MYERROR1" },
	{ 2000, "MYERROR2" },
	...
	};

Łączenie ( konkatenacja )

Operator konkatenacji (podwójny znak hash) łączy sąsiadujące tokeny w treści rozwinięcia:

#define CONCAT( x,y ) x##y 

//użycie:
CONCAT(a,b)
 
//prowadzi do:
ab

Zaskakujące może być jednak gdy jako argument poda się inny symbol preprocesora. Okazuje się bowiem, że nie zostanie on przetworzony przed połączeniem a wstawiony "dosłownie".

#define my_symbol 100
//użycie:
CONCAT(q,my_symbol)
 
//prowadzi do:
qmy_symbol

//a nie do spodziewanego
q100

Aby osiągnąć zamierzony cel, potrzebne jest użycie dodatkowego pośredniczącego makra, które przetworzy argument wejściowy.

#define _CONCAT( x,y ) x##y 
#define CONCAT( x,y ) _CONCAT(x,y)

#define my_symbol 100
//użycie:
CONCAT(q,my_symbol)
 
//zostanie rozwinięte do:
q100

Predefiniowane symbole

Preprocesor posiada kilka użytecznych, predefiniowanych symboli:

-- SYMBOL --      --Znaczenie--            --Przykładowe rozwinięcie--

__LINE__       bieżący numer linii          37
__FILE__       aktualnie przetwarzany plik  "test.c"
__DATE__       bieżąca data                 "Dec 17 2014"
__TIME__       bieżący czas                 "10:39:02"
__COUNTER__    licznik, zwiększany po każdym użyciu    0

Mogą one być użyte do:

  • oznaczenia miejsca wystąpienia błędu (nr linii, plik)
  • oznaczenia wersji programu - czasu kompilacji,
  • nadawania unikalnych, kolejnych oznaczeń

Linki

  1. Dokumentacja preprocesora na stronach gcc/GNU