Operační systém UNIX a jazyk C

Jan Brodský
Luděk Skočovský

SNTL Nakladatelství technické literatury Praha 1989, ISBN 80-03-00049-1

5. Programovací jazyk C


5.7 Textový makroprocesor

Textový makroprocesor předzpracovává zdrojový text programu a předává ho dalším částem kompilátoru. Kromě náhrady identifikátorů řetězci a vkládání souborů (odst. 5.1.7), umožňuje definovat a využívat makra a podmíněný překlad. Jeho činnost je řízena příkazy, které jsou vyznačeny znakem # v první pozici řádku. Mezi tímto znakem a vlastním příkazem může být libovolný počet mezer (a tabulátorů).

Formát příkazů makroprocesoru nezávisí na jazyku C. Příkazy se mohou objevit na libovolném místě programu a platí do konce zdrojového souboru nebo do příkazu makroprocesoru, který ruší jejich význam.

5.7.1 Náhrada identifikátorů

S touto náhradou jsme se již seznámili v odst. 5.1.7. Má tvar

#define identifikátor řetězec
a způsobí, že makroprocesor v dalším textu programu nahradí identifikátor řetězcem. Význam takto definovaného identifikátoru končí s koncem zdrojového programu nebo s výskytem příkazu
#undefine identifikátor

Případný středník je součástí řetězce (neukončuje příkaz!). Příkazy #define s prázdným řetězcem, např.

#define DEBUG
#define VAX
se používají k řízení podmíněného překladu, odst. 5.7.3.

Často používanou konstrukcí je definice konstant

#define BLOKSIZE 512
#define BITSOFCHAR 8
#define BITSOFINT (sizeof(int) * BITSOFCHAR
nebo pomůcek zlepšujících čitelnost programů v C
#define INC ++
#define EQUALS ==
#define THEN
Můžeme pak psát např.
if(x EQUALS y)THEN
  INC x;
  ...
přičemž kompilátor po zpracování makroprocesorem překládá text:
if(x == y)
  ++x;
  ...

5.7.2 Makra

Příkaz #define zajistí náhradu identifikátoru řetězcem. Zobecněním tohoto principu je makro, kdy dochází k záměně několika identifikátorů současně. Příkazem

#define identifikátor-makro(formální parametry) řetězec
se definuje identifikátor-makro, za kterým musí ihned (tj. bez mezery, tabulátoru aj.) následovat levá okrouhlá závorka, seznam formálních parametrů, identifikátorů oddělených čárkami a pravá okrouhlá závorka. V definujícím řetězci se vyskytují formální parametry, jinak by mohl být seznam formálních parametrů vypuštěn. Například
#define abs(x) (((x)<0)?-(x):(x))
definuje makro abs s jedním formálním parametrem x, který je obsažen i v definujícím řetězci. Makroprocesor každý další výskyt identifikátoru makra se seznamem řetězců v okrouhlých závorkách, tj. volání makra, nahradí definujícím řetězcem. Formální parametry v něm přitom nahradí odpovídajícími řetězci z volání makra, tedy např. volání
z = abs(3*y-15);
makroprocesor nahradí tak, že kompilátor bude překládat
z = (((3*y-15)<0)?-(3*y-15):(3*y-15));

Nyní je snad zřejmé, proč je vhodné v definičním řetězci používat hojně závorek. Mechanismus makra připomíná mechanismus funkcí. Liší se v tom, že se přeloží na přímý kód s menší režií než volání funkce. Kolikrát je parametr použit v definičním řetězci, tolikrát se vyhodnocuje. Poslední příklad to ukazuje dostatečně zřetelně. Přesto se makra používají v knihovně standardních funkcí. Například funkce

isascii(c)
je vlastně makrem s definicí:
#define isascii(c) ((unsigned)(c)<=0177).

5.7.3 Podmíněný překlad

Podmíněný překlad je řízen příkazy #if, #else, #elif a #endif. Je tedy lépe strukturován než podmíněný příkaz jazyka C.

Podmínka testovaná v příkazu #if musí být konstantním výrazem (bez přetypování, sizeof a výčtových konstant). Vyhodnotí-li se jako nenulová, text následující za příkazem #if až do prvního výskytu příkazu #else, #elif nebo #endif se do programu zařadí, jinak ne. V podmínce se může vyskytnout podvýraz tvořený unárním operátorem defined a identifikátorem, např.

defined DEBUG
nebo ve tvaru
defined (DEBUG).

Takový výraz se vyhodnotí jako 1, je-li identifikátor definován, jinak jako nula. Příkaz

#ifdef DEBUG
je zkratkou
#if defined (DEBUG),
analogicky
#ifndef DEBUG
je zkratkou za
#if ! defined (DEBUG).

5.7.4 Vkládání souborů

Místo příkazu #include (odst. 5.1.7) makroprocesor zařadí obsah souboru, jehož jméno je v příkazu uvedeno. Je-li jméno souboru uzavřeno do uvozovek, hledá se soubor nejprve v adresáři, v němž se nachází zdrojový program, a teprve potom ve standardních adresářích. Je-li v úhlových závorkách <, >, hledá se jen ve standardních adresářích. Standardní adresáře jsou většinou podadresáře /usr/include. Příkladem mohou být zápisy:

#include <stdio.h>
nebo
#include "KPOC.h"
Příkazy #include mohou být vnořeny.

5.7.5 Příkaz #line

Programy v jazyku C vznikají i jako produkty jiných programů, tzv. generátorů (např. lex, yacc apod.). Chybová hlášení kompilátoru C se vztahují k překládanému souboru a řádku v tomto souboru, nikoliv k původnímu zdrojovému souboru pro generátor. Příkazem

#line řádek "soubor"
makroprocesor zajistí, že kompilátor považuje text programu, následující za tímto příkazem, za obsah řádku v uvedeném souboru. Případné chybové hlášení je pak vztaženo k tomuto řádku.