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    << Preprocesor C - szczegóły   

Preprocessor C - zaawansowane wykorzystanie (preprocesor GNU)

2014-12-18    dr inż. Piotr Romaniuk

Spis treści

Wstęp
Identyfikator symboliczny
Wymuszenie średnika
Wybieranie makra wartością parametru
Wyznaczenie liczby argumentów
Sprawdzenie czy podane zostały argumenty
Preprocesor - podstawy (osobny wpis)
Preprocesor - szczegóły (osobny wpis)

Wstęp

Preprocesor może zostać wykorzystany do wygenerowania fragmentów kodu w czasie budowania programu (compilation time). Przy zaawansowanym wykorzystaniu jego zdawałoby się skromnych możliwosci można stworzyć pewien metajęzyk wspierający programowanie w C.
Dużo ciekawych i zaskakujących pomysłów można znaleźć w
bibliotece boost, w części preprocessor. Wiele z tych przykładów opiera się na wielokrotnym wypisaniu różnych wyrażeń oraz wykorzystaniu specyfiki rozwijania argumentów i makr, wliczając operator łączenia (konkatenacji).
Poniżej zostało przedstawionych kilka ciekawych przykładów.

Identyfikator symboliczny

Często pojawia się potrzeba sprawdzenia wersji i warunkowej konfiguracji/kompilacji fragmentów kodu. Przykładowo, załóżmy, że potrzeba zdefiniować położenie końcówki GPIO zależnie od rodzaju płytki:

// określenie rodzaju płytki (w nagłówku specyfikującym):
#define IDBOARD "CNC_ROUTER_HEAD3"

// poniższe w nagłówku ogólnym:
#if IDBOARD=="CNC_ROUTER_HEAD3" 
# define MOTOR_PIN 1
# define MOTOR_PORT 1
#elif IDBOARD=="CNC_ROUTER_CUTTER"
# define MOTOR_PIN 3
# define MOTOR_PORT 1
#endif

// podczas próby zbudowania preproecsor wygeneruje bład:
test.c:114:12: error: token ""CNC_ROUTER_HEAD3"" is not valid in preprocessor expressions
#define IDBOARD "CNC_ROUTER_HEAD3"
                ^
test.c:120:5: note: in expression of macro 'IDBOARD'
 #if IDBOARD=="CNC_ROUTER_HEAD3"

Problemem okazuje się tu brak możliwości porównania tekstowego w #if - preprocesor nie ma takiego operatora, może porównywać tylko wartości całkowite. Aby to rozwiązać można zdefiniować typy płytek w postaci makr rozwijanych do unikalnych identyfikatorów:

#define IDBOARD CNC_ROUTER_HEAD3

//identyfikatory dla różnych maszyn:
#define CNC_ROUTER_HEAD3 1
#define CNC_ROUTER_CUTTER 2

#if IDBOARD==CNC_ROUTER_HEAD3 
# define MOTOR_PIN 1
# define MOTOR_PORT 1
#elif IDBOARD==CNC_ROUTER_CUTTER
# define MOTOR_PIN 3
# define MOTOR_PORT 1
#endif

Wymuszenie średnika

Przy użyciu makra często pojawia się niejednoznaczność czy należy po nim stawiać średnik (aby przypominało to składnię C) czy nie. Najlepiej byłoby gdyby to implementator makra tak je skonstruował aby brak średnika powodował błąd kompilacji. Problem staje się jeszcze poważniejszy, gdy dotyczy to makr wieloliniowych i ich współpracy z instrukcjami warunkowymi. Oczekuje się, że makro powinno dać się użyć w dowolnym miejscu (np. po if/else) i powinno się ono zachować tak jakby było funkcją.

// brak ochrony spójności makra
#define MULTILINE_MACRO( x ) fun( x );\
			     fun2()
	
//problem:
	if( a>3 ) MULTILINE_MACRO(a);
//skompiluje sie do:
	if( a>3 ) fun( x );
	fun2();		  

Jak widać tylko pierwsza częśc makra (wywołanie fun()) będzie warunkowe, druga linia (tj. fun2()) będzie wywoływane zawsze. Z pewnoscią nie o to chodziło autorowi. Warto zauważyć, że wszystko się skompiluje ale nie będzie działać właściwie! Dodajmy więc nawiasy klamrowe:

#define MULTILINE_MACRO( x ) {\
				fun( x );\
			     	fun2();\
			     }
	
	if( a>3 ) MULTILINE_MACRO(a);
//skompiluje sie do:
	if( a>3 ) {
		fun( x );
		fun2();		  
		};

Jest już lepiej ale makro nie wymusza na programiście stawiania średnika. Do tego nie ma jednolitej reguły, jeśli bowiem jest else w instrukcji warunkowej to średnika nie należy stawiać bo prowadziłoby to do błędu:

	if( a>3 ) MULTILINE_MACRO(a);
	else ...
//wygeneruje błąd:
test1.c:136:3: error: 'else' without a previous 'if'

Przeglądając źródła kernela Linuxa można natknąć się na rozwiązanie:

#define MULTILINE_MACRO( x ) \
	do{\
		fun( x );\
	     	fun2();\
	  }while(0)

Taki kod wymusza postawienie średnika (zawsze) i jest pewność że ta pętla wykona się raz i tylko raz, a do tego w całości (jest niepodzielna), niezależnie od tego gdzie się znajdzie.

Wybieranie makra wartością parametru

Przypuśćmy, że jakiś symbol rozwija się do 0 lub 1-ki. Chcielibyśmy, aby makro rozwijało się do różnych symboli zależnie od tego parametru. Wystarczy stworzyć dwa makra, których nazwy będą tworzone przez połaczenie stałej części (np. SELECT_) oraz wartości symbolu kontrolnego:

//makro wybierające
#define SELECT( sym_true, sym_false, ctrl ) _SELECT( sym_true, sym_false, ctrl )
#define _SELECT( sym_true, sym_false, ctrl ) SELECT_##ctrl( sym_true, sym_false )

//dwa makra do wybierania odpowiedniego argumentu
#define SELECT_0( sym_true, sym_false ) sym_false
#define SELECT_1( sym_true, sym_false ) sym_true

//wykorzystanie
#define SOME_CTRL_SYM 1
SELECT( SYMBOL_T, SYMBOL_F, SOME_CTRL_SYM )  => SYMBOL_T

//a gdy symbol bedzie zdefiniowany jako 0:
#define SOME_CTRL_SYM 0
SELECT( SYMBOL_T, SYMBOL_F, SOME_CTRL_SYM )  => SYMBOL_F

Należy tu zwrócić uwagę, że symbol kontrolny może być wynikiem jakiegoś innego przetwarzania. W powyższym przykładzie jest zdefiniowany #definem aby zademonstrować jak najprościej jak jest używany. Podobnie wynik takiej selekcji może być rozwinięciem innego makra. Zastosowań jest więc wiele.

Wyznaczenie liczby argumentów

Załóżmy że chcemy okreslić liczbę argumentów podanych do makra. Następnie w zależności od tej liczby wywoływać różne zachowania. (wystarczy połączyć jaki stały tekst z otrzymaną liczbą przez konkatenację i zdefiniować różne makra dla takich symboli). Można powiedzieć, że jest to pewnego rodzaju uproszczone przeciążanie (overloading).

// niech GET_ARGS_NB( ) będzie makrem rozwijanym do liczby podanych mu argumentów

// najpierw zdefiniujmy konkatenację (łączenie), które będzie przetwarzać argumenty (preprocesor tego nie robi,
// gdy jest operator konkatenacji.
#define _CONC( a, b ) a ## b
#define CONC( a, b ) _CONC( a, b )

// zdefiniujmy dwie wersje zachowania w zależności od tego ile ejst argumentów (na razie dla 1 i 2)
#define FN_ARGS_1( x )    some_fn_arg1( x )
#define FN_ARGS_2( x, y ) some_fn_args2( x, y )

// a teraz definicja przełącznika, głównej funkcji używanej w kodzie ze zmienną liczbą argumentów:
#define SOME_FN_VARARGS( ... ) CONC( FN_ARGS_, GET_ARGS_NB( __VA_ARGS__ ) )( __VA_ARGS__ )

// jeśli makro wyznaczające liczbę argumentów pracuje poprawnie to poniższe wywołania rozwiną się odpowiednio:
SOME_FN_VARARGS( x ) 	  => some_fn_arg1( x )
SOME_FN_VARARGS( x, y )   => some_fn_args2( x, y )

Powyżej pokazane zostało jak może być wykorzystane makro wyznaczające liczbę argumentów, a teraz jak je zrobić:

#define GET_6TH( n1, n2, n3, n4, n5, n6, ...) n6

#define GET_ARGS_NB( ... )  GET_6TH( __VA_ARGS__  , 5, 4, 3, 2, 1)

Pomysł opiera się na tym, aby do listy argumentów dokleić kolejne liczby naturalne a potem zawsze czytać wartość z określonej pozycji. Im więcej będzie argumentów tym bardziej przesunięte będą te doklejone.

Sprawdzenie czy podane zostały argumenty

Kolejny problem do rozwiązania przy pomocy preprocesora: sprawdzenie czy makro zostało wywołane bez argumentów. Podobnie jak poprzednio, można tu zaimplementować inne zachowanie dla wywołania z argumentami lub bez.

#define GET_7TH( n1, n2, n3, n4, n5, n6, n7, ...) n7

#define IS_EMPTY( ... )  GET_7TH( 1000, ## __VA_ARGS__  , 0, 0, 0, 0, 0, 1, 1001)

//wykorzystanie:
IS_EMPTY( a )	=> 0
IS_EMPTY( )     => 1

Liczba 1000 jest tylko po to aby był jakiś argument przed ## __VA_ARGS__ - konieczne aby się sklejało gdy brak argumentów, liczba 1001 jest po to aby dostarczyć dodatkowy argument do makra GET_7TH.
Jak to dobrze, że jest ##__VA_ARGS__, bez tego trzeba byłoby konstruować to samo z 7 makr i całkiem niezłego przekształcania (zobacz w bibliotece boost).

Linki

  1. Biblioteka boost
  2. Opis makrodefinicji
  3. Opis dotyczący argumentów makr