-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbook_chap10.tex
161 lines (109 loc) · 8.35 KB
/
book_chap10.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
\chapter{De preprocessor}
\label{cha:preprocessor}
\thispagestyle{empty}
De C-compiler is uitgerust met een \textsl{preprocessor}\index{preprocessor}. Dit kan een apart programma zijn dat door de C-compiler zelf gestart wordt, of het kan ingebed zijn in de C-compiler.
De preprocessor is \textsl{geen} compiler in de zin van het genereren van uitvoerbare code voor een computer. De preprocessor verwerkt het C-bestand aan de hand van bepaalde opdrachten die allemaal beginnen met een \textsl{hash}\footnote{Met een woord erachter wordt dit een hashtag genoemd.} (\texttt{\#}). Die worden \textsl{directives} genoemd. De uitvoer van de preprocessor is een bestand waarin geen directive meer voorkomt en dat bestand wordt aan de C-compiler doorgegeven. De C-compiler heeft dus geen weet van directives.
\section{Invoegen van bestanden}
\index{include@{\texttt{\#include}}, preprocessor directive}
Een bestand kan in een C-programma worden ingevoegd door de \textsl{include directive}. Deze heeft de vorm:
\hspace*{1em}\texttt{\#include <}\textsl{bestandsnaam}\texttt{>}
of
\hspace*{1em}\texttt{\#include "{}}\textsl{bestandsnaam}\texttt{"}
Invoegen gebeurt op het punt waar \texttt{\#include} voorkomt. De include directive zelf wordt verwijderd. Een bestandsnaam kan tussen \texttt{<} en~\texttt{>} staan. Dan wordt gezocht in zogenoemde \textsl{systeemmappen}. Welke dat zijn, hangt van de toolchain af. Een bestandsnaam kan ook tussen aanhalingstekens staan. Dan wordt eerst in de map gezocht waarin ook het C-programma geplaatst is en, als het bestand niet gevonden is, in de systeemmappen. De include directive wordt typisch gebruikt om een \textsl{header-bestand}\index{header-bestand} aan het begin van een C-programma in te lezen. We hebben er al een aantal gebruikt:
\hspace*{1em}\texttt{\#include <stdio.h>}\\
\hspace*{1em}\texttt{\#include <stdint.h>}\\
\hspace*{1em}\texttt{\#include <string.h>}\\
\hspace*{1em}\texttt{\#include <math.h>}
De extensie \texttt{.h} geeft aan dat het om een header-bestand gaat. Dit is niet verplicht maar wel \textsl{good practice}. Het is \textsl{niet} de bedoeling om andere C-bestanden via de include directive in te lezen. Dat kan wel, maar is \textsl{bad practice}.
Header-bestanden kunnen zelf ook include directives bevatten, wat ook vaak het geval is. Daarbij moet erop gelet worden dat header-bestanden niet meerdere keren geladen worden. Hoe dat vermeden kan worden, zien we in paragraaf~\ref{sec:conditioneelinlezen}.
\section{Macrovervanging}
\index{define@{\texttt{\#define}}, preprocessor directive}
Een \textsl{macro}\index{macro} in de eenvoudigste vorm, ziet eruit als:
\hspace*{1em}\texttt{\#define }\textsl{macronaam vervangende-tekst}
Overal waar \textsl{macronaam} voorkomt wordt dit vervangen door \textsl{vervangende-tekst} behalve in C-strings. De \textsl{macronaam} moet exact overeenkomen, dus als \texttt{MAX} gedefinieerd is, dan wordt er geen vervanging gedaan in \texttt{printf("{}MAX")} en \texttt{MAX\_GETAL}. Macro's mogen zelf ook weer macro's bevatten en het vervangen gaat net zolang door totdat er geen vervangingen meer worden gedaan. We noemen dit \textsl{macro-expansie}\index{macro-expansie}.
Een macro kan argumenten bevatten zodat de vervangende tekst voor verschillende doeleinden gebruikt kan worden. Een klassiek voorbeeld is:
\hspace*{1em}\texttt{\#define max(A, B) ((A) > (B) ? (A) : (B))}
De macro \texttt{max(A, B)} ziet eruit als een functie maar dat is het niet. Overal waar \texttt{max} voorkomt wordt het vervangen door de vervangende tekst. Merk op het gebruik van de vele haakjes. Dat is nodig om de argumenten correct uit te rekenen:
\hspace*{1em}\texttt{x = max(p+q, r+s);}
wordt dan vervangen door
\hspace*{1em}\texttt{x = ((p+q) > (r+s) ? (p+q) : (r+s));}
Een veelgemaakte fout is het als volgt definiëren van een kwadraat:
\hspace*{1em}\texttt{\#define sqr(x) x * x \ \ \ \ /* WRONG */}
en dat geeft een verkeerd antwoord als \texttt{x} wordt vervangen door \texttt{y+1}. De correcte manier is om extra haakjes te gebruiken:
\hspace*{1em}\texttt{\#define sqr(x) ((x) * (x)) \ \ \ \ /* correct */}
Het kan alsnog verkeerd gaan als we de increment- of decrement-operator gebruiken:
\hspace*{1em}\texttt{j = sqr(++i);}
Dit wordt geëxpandeerd tot \lstc{j = ((++i)} \lstc{* (++i))} dus i wordt twee keer verhoogd.
Een voordeel van een macro is dat de argumenten onafhankelijk zijn van het datatype. Als we een floating point-getal willen kwadrateren, dan kan dat ook:
\hspace*{1em}\texttt{double j = sqr(2.16);}
We kunnen de preprocessor ook een macro laten vergeten:
\index{undef@{\texttt{\#undef}}, preprocessor directive}
\hspace*{1em}\texttt{\#undef }\textsl{macronaam}
\section{Conditionele compilatie}
Het gebruik van macro's is bijzonder handig als we delen van een C-bestand wel of niet willen laten compileren. Een veelgebruikt voorbeeld is het afdrukken van debug-informatie:
\begin{lstlisting}[caption=Conditionele compilatie.]
#include <stdio.h>
#define DEBUG 1
int main(void) {
// ...
#if defined(DEBUG)
printf("Variabele: %d\n", a);
#endif
// ...
}
\end{lstlisting}
\index{if@{\texttt{\#if}}, preprocessor directive}
\index{endif@{\texttt{\#endif}}, preprocessor directive}
\index{defined@{\texttt{defined}}, preprocessor directive}
In plaats van \texttt{defined} mogen we ook \texttt{\#ifdef} gebruiken. We kunnen ook header-bestanden aan de hand van macro's laten invoegen. De diverse C-compilers definiëren bepaalde macro's zodat deze in een C-bestand te gebruiken zijn, de zogenoemde \textsl{predefined macros}\index{predefined macros}. Een voorbeeld is te zien in listing~\ref{cod:precondinc}.
\begin{lstlisting}[caption=Conditioneel inlezen van header-bestanden.,label=cod:precondinc]
#include <stdio.h>
#ifdef __GNUC__ /* GNU C-compiler */
#define VSTRING "GNU C-compiler"
#include <gnuc.h>
#endif
#ifdef _MSC_VER /* Microsoft C-compiler
#define VSTRING "Visual Studio C-compiler"
#include <ms.h>
#endif
#ifdef __clang__ /* Clang C-compiler
#define VSTRING "Clang C-compiler"
#include <clang.h>
#endif
int main(void) {
printf("Gecompileerd met " VSTRING);
return 0;
}
\end{lstlisting}
\index{ifdef@{\texttt{\#ifdef}}, preprocessor directive}
\index{endif@{\texttt{\#endif}}, preprocessor directive}
Welke predefined macros gedefinieerd zijn, is afhankelijk van de C-compiler. De documentatie moet hiervoor geraadpleegd worden. Sommige macro's hebben ook nuttige waarden zoals te zien is in listing~\ref{cod:premscver}.
\begin{lstlisting}[caption=Gebruik van de \texttt{\_MSC\_VER}-macro.,label=cod:premscver]
#ifdef _MSC_VER
#if _MSC_VER == 1926
#define VSTRING "Visual Studio 2019 versie 16.6"
#elif _MSC_VER == 1925
#define VSTRING "Visual Studio 2019 versie 16.5"
#elif _MSC_VER == 1924
#define VSTRING "Visual Studio 2019 versie 16.4"
#else
#define VSTRING "Visual Studio 2019 versie 16.3 of ouder"
#endif
#endif
\end{lstlisting}
\index{elif@{\texttt{\#elif}}, preprocessor directive}
\index{else@{\texttt{\#else}}, preprocessor directive}
Meerdere macro's mogen in een logische expressie voorkomen. De onderstaande regels zorgen ervoor dat er conditioneel gecompileerd wordt als de macro \texttt{\_\_linux\_\_} wel bestaat en de macro \texttt{\_\_ANDROID\_\_} niet bestaat.
\hspace*{1em}\texttt{\#if defined(\_\_linux\_\_) \&\& !defined(\_\_ANDROID\_\_)}
In principe mag de expressie willekeurig complex zijn, als het maar resulteert in een constante integer. Typische C-constructies kunnen niet gebruikt worden, zoals (C-)variabelen, \texttt{sizeof} en expliciete type casts want de preprocessor is geen C-compiler.
\section{Conditioneel inlezen van een bestand}
\label{sec:conditioneelinlezen}
Om ervoor te zorgen dat tijdens een compilatie van een C-bestand een header-bestand niet meer dan één keer wordt ingelezen, kunnen we een macro gebruiken. Aan het begin van het header-bestand testen we of een bepaalde macro bestaat. Als dat \textsl{niet} zo is, dan definiëren we de macro en laden de rest van het header-bestand. De volgende keer dat het header-bestand (in dezelfde compilatieslag) wordt geladen is de macro gedefinieerd en wordt de code overgeslagen. Zie listing~\ref{cod:preheaderinc}.
\begin{lstlisting}[caption=Gebruik van een macro om een header-bestand één keer in te lezen.,label=cod:preheaderinc]
/* File myheader.h */
#ifndef _MYHEADER_H
#define _MYHEADER_H
// ... contents goes here
#endif
\end{lstlisting}
\index{ifndef@{\texttt{\#ifndef}}, preprocessor directive}