-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathpreprocessor.tex
622 lines (507 loc) · 30.7 KB
/
preprocessor.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
\section{Препроцессирование}
Препроцессирование это первая стадия компиляции. Препроцессирование выполняется программой называемой препроцессор. Пропроцессор получает на вход файл исходного кода с расширением .cpp. Вывод препроцессора передается на следующую стадию компиляции --- транслятору.
Препроцессор проходит по входному файлу, копируя текст в выходной файл. Если он встречает строку начинающуются с символа {\bf \#}, он выполняет команду, которая записана после {\bf \#}. Такие команды называются директивами препроцессора.
\subsection{Директивы препроцессора}
Существуют следующие директивы препроцессора:
\begin{enumerate}
\item \#define
\item \#elif
\item \#else
\item \#error
\item \#endif
\item \#if
\item \#ifdef
\item \#ifndef
\item \#include
\item \#pragma
\item \#undef
\end{enumerate}
\subsubsection{Директива \#include}
Директива {\bf \#include} имеет две формы
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#include <somefile.h>
#include "somefile.h"
\end{minted}
Когда препроцесор встречает эту директиву, он подставляет на её место текст файла {\bf somefile.h}. При этом текст файла {\bf somefile.h} тоже препроцессируется. Если в нём есть какие-то директивы то они будут выполнены.
Пусть даны следующие файлы
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// print.h
void print(char const*);
// main.cpp
#include "print.h"
int main()
{
print("Hello, world");
}
\end{minted}
Результатом препроцессирования {\bf main.cpp} будет:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
void print(char const*);
int main()
{
print("Hello, world");
}
\end{minted}
В этом можно убедиться --- выполним команду {\bf g++ -E main.cpp}. Ключ {\bf -E} говорит драйверу, что нужно лишь отпрепроцессировать входной файл, не выполняя дальнейшие стадии компиляции.
Форма {\bf \#include <somefile.h>} производит поиск файла {\bf somefile.h} в списке каталогов, называемых include directories. Их можно указать компилятору опцией {\bf -I}. Например:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{sh}
g++ -Isome/directory -Ianother/directory -I../and/another/one myfile.cpp
\end{minted}
Файлы ищутся в этих директориях слева направо. Препроцессор автоматически добавляет в конец этого списка пути к директориям в которых лежат хедера стандартной библиотеки.
Форма {\bf \#include ``somefile.h''} производит поиск файла {\bf somefile.h} сначала в текущем каталоге, а потом в include directories.
\subsubsection{Директива \#define}
Рассмотрим директиву {\bf \#define} на примере:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define PI 3.14159265
double circumference(double r)
{
return 2 * PI * r;
}
\end{minted}
Здесь директива {\bf \#define} определяет макрос с именем {\bf PI}. Текст, который идет после имени макроса, называется replacement'ом. Replacement отделяется от имени макроса пробелом и распростроняется до конца строки. Все вхождения идентификатора {\bf PI} ниже этой директивы будут заменены на replacement. Результатом препроцессирования примера выше является следующий текст:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
double circumference(double r)
{
return 2 * 3.14159265 * r;
}
\end{minted}
Приведенная выше форма директивы {\bf \#define} называется object-like. Существует вторая форма этой директивы, называемая function-like:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define MIN(x, y) x < y ? x : y
printf("%d", MIN(4, 5));
\end{minted}
Результатом препроцессирования этого фрагмента кода является:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
printf("%d", 4 < 5 ? 4 : 5);
\end{minted}
Важно знать, что препроцессор, выполняя подстановки макросов, ничего не знает про приоритет арифметических операций и синтаксическую структуру программы. Рассмотрим следующую программу:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define MIN(x, y) x < y ? x : y
int main()
{
printf("%d", 10 + MIN(4, 5));
}
\end{minted}
Данная программа выводит {\bf 5}, тогда как скорее всего программист ожидал вывода {\bf 14}. Дело в том, что после раскрытия макроса возникает выражение {\bf 10 + 4 < 5 ? 4 : 5}. Поскольку бинарный {\bf +} имеет приоритет выше, чем у тернарного оператора, данное выражение разбирается транслятором как {\bf (10 + 4) < 5 ? 4 : 5}, а не {\bf 10 + (4 < 5 ? 4 : 5)}, как мог ожидать программист, использующий макрос. Чтобы избегать подобных проблем, у function-like макросов, которые раскрываются в выражение, replacement следует брать в скобки. По той же причине имена параметров макроса в replacement, следует брать в скобки. Корректный макрос {\bf MIN} мог бы выглядеть следующим образом:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define MIN(x, y) ((x) < (y) ? (x) : (y))
\end{minted}
Директива {\bf define} позволяет определять макросы повторно, при этом, в каждой точке программы силу имеет последний {\bf define} данного макроса:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
X
#define X foo
X
#define X bar
X
\end{minted}
раскрывается в:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
X
foo
bar
\end{minted}
Replacement макроса не препроцессируется при определении макроса, но результат раскрытия макроса препроцессируется повторно:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define Y foo
#define X Y
#define Y bar
X // раскрывается в bar
\end{minted}
Что произойдет если replacement макроса {\bf M} будет содержать использование макроса {\bf M}? В этом случае возникает рекурсия. По спецификации препроцессор никогда не должен раскрывать макрос {\bf M} изнутри самого себя, а оставлять вложенный идентификатор как есть:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define M { M }
M // раскрывается в { M } один раз, второй раз M не раскрывается
\end{minted}
Ещё пример:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define A a{ B }
#define B b{ C }
#define C c{ A }
A
B
C
\end{minted}
Результат препроцессирования:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
a{ b{ c{ A } } }
b{ c{ a{ B } } }
c{ a{ b{ C } } }
\end{minted}
\subsubsection{Директива \#undef}
Директива {\bf \#undef} позволяет разопределить макрос, определенный ранее с помощью директивы {\bf \#define}. Пример:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
X
#define X foo
X
#undef X
X
\end{minted}
Результат препроцессирования:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
X
foo
X
\end{minted}
\subsubsection{Семейство директив \#if}
Директивы {\bf \#ifdef}, {\bf \#ifndef}, {\bf \#if}, {\bf \#else}, {\bf \#elif}, {\bf \#endif} позволяют отпрепроцессировать часть файла, лишь при определенном условии. Директивы {\bf \#ifdef}, {\bf \#ifndef} проверяют определен ли указанный макрос. Например:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#ifdef __x64_64__
typedef unsigned long uint64_t;
#else
typedef unsigned long long uint64_t;
#endif
\end{minted}
Директива \#if позволяет проверить произвольное арифметическое выражение.
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define TWO 2
#if TWO + TWO == 4
// ...
#endif
\end{minted}
Директива {\bf \#if} препроцессирует свой аргумент, а затем парсит то, что получилось как арифметическое выражение. Если после препроцессирования в аргументе {\bf \#if} остаются идентификаторы, то они заменяются на 0, кроме идентификатора {\bf true}, который заменяется на 1.
\subsubsection{Директива \#error}
Директива {\bf \#error} обрывает препроцессирование с ошибкой. Ей можно указать сообщение которое будет показано в ошибке. Например:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#if __BYTE_ORDER == __BIG_ENDIAN
// ...
#elif __BYTE_ORDER == __LITTLE_ENDIAN
// ...
#else
#error unknown endianness
#endif
\end{minted}
\subsubsection{Директива \#pragma}
Директива {\bf \#pragma} это директива поведение, которой может быть определено каждым компилятором по своему (implementation-defined behavior). То, какие формы директивы {\bf \#pragma} поддерживает конкретный компилятор, можно узнать в документации. Например, компилятор Microsoft Visual C++ поддерживает 41\footnote{\url{https://msdn.microsoft.com/en-us/library/d9x1s805.aspx}} вид разных прагм.
Наиболее широко используемой прагмой является {\bf \#pragma once}, рассмотренная в разделе \ref{pragma_once}.
\subsection{Защита от повторного включения}
При препроцессировании директива {\bf \#include} заменяется на текст указаного хедер-файла. Повторное включение хедер файл может привести к тому, что декларации во втором включении конфликтуют с декларациями в первом включении. Рассмотрим следующую программу:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// mytype.h
struct mytype
{
int abc;
};
// somefile.cpp
#include "mytype.h"
#include "mytype.h"
\end{minted}
Препроцессирование {\bf somefile.cpp} выдаёт следующий результат:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
struct mytype
{
int abc;
};
struct mytype
{
int abc;
};
\end{minted}
Компиляция {\bf somefile.cpp} ожидаемо приводит к ошибке:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
In file included from somefile.cpp:2:0:
mytype.h:1:8: error: redefinition of ‘struct mytype’
struct mytype
^~~~~~
In file included from somefile.cpp:1:0:
mytype.h:1:8: error: previous definition of ‘struct mytype’
struct mytype
^~~~~~
\end{minted}
Как видно из ошибки структура {\bf mytype} сконфликтовала с собой же.
В данной ситуации можно было бы посоветовать не инклудить хедер дважды. Однако это не всегда легко сделать. Предположим, что существует два хедера {\bf a.h} и {\bf b.h}, которые используют {\bf mytype} из {\bf mytype.h}, а файл {\bf somefile.cpp} использует оба хедера {\bf a.h} и {\bf b.h}. Следует ли в {\bf a.h} и в {\bf b.h} инклудить {\bf mytype.h}? Если ответить на этот вопрос положительно, то это приведет к аналогичной ошибке, за тем лишь исключением, что {\bf mytype.h} инклудиться не напрямую в {\bf somefile.cpp}, а косвенно --- первый раз через {\bf a.h}, второй --- через {\bf b.h}. Если ответить на этот вопрос отрицательно, то каждый пользователь файлов {\bf a.h} будет обязан перед инклудом {\bf a.h} проинклудить {\bf mytype.h}. Для одного хедер-файла это может показаться приемлемым, однако, если программа состоит из сотен хедеров, каждый требующий десятки других хедеров, этот подход становится неприемлемым.
Решением данной проблемы является написание хедера таким образом, что его возможно включать несколько раз и это не приводит к ошибке. Один возможный способ это сделать показан на следующем примере:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// mytype.h
#ifndef MYTYPE_H
#define MYTYPE_H
struct mytype
{
int abc;
};
#endif
\end{minted}
Такая тройка директив {\bf \#ifndef}, {\bf \#define}, {\bf \#endif} называется {\bf include guard'ом}. При включении файла первый раз макрос {\bf MYTYPE\_H} неопределен, поэтому текст файла будет подставлен, а макрос {\bf MYTYPE\_H} станет определен. При повторном включении макрос {\bf MYTYPE\_H} уже определен и содержимое файла пропускается.
При использовании include guard'ов важно, чтобы макрос, который проверяется и определяется был уникальный для каждого файла. Обычно для этого используют имя макроса образованное от имени файла.
\subsubsection{\#pragma once}
\label{pragma_once}
Вторым способом защитить файл от повторного включения является использование директивы {\bf \#pragma once}:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// mytype.h
#pragma once
struct mytype
{
int abc;
};
// somefile.cpp
#include "mytype.h"
#include "mytype.h"
\end{minted}
Директива {\bf \#pragma once} говорит препроцессору, что текст файла следует подставлять лишь при первом включение. Препроцессирование {\bf somefile.cpp} из примера выше выдаёт:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
struct mytype
{
int abc;
};
\end{minted}
Преимуществом использования {\bf \#pragma once} по сравнению с include guard является то, что не требуется изобретать новое имя макроса для каждого нового файла. Это исключает ошибку в случае если макросы совпадут для разных файлов.
Недостатком использования {\bf \#pragma once} является то, что она не входит в стандарт C++, а является лишь расширением, пусть и очень популярным, компиляторов. Эта директива поддерживается очень широко, поэтому её использовании маловероятно приведет к проблемам с переносимостью на практике. GCC, clang, Microsoft Visual C++, Intel C++ все поддерживают эту директиву.
\subsubsection{Рекурсивное включение}
\label{recursive_include}
Иногда, при не очень аккуратном программировании возникает ситуация, когда хедер {\bf a.h} инклудит {\bf b.h}, а хедер {\bf b.h} инклудит {\bf a.h}. При отсутствии инклуд-guard'ов это привело бы к ошибке компиляции:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// a.h
#include "b.h"
// b.h
#include "a.h"
// c.cpp
#include "a.h"
\end{minted}
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
In file included from b.h:1:0,
from a.h:1,
from b.h:1,
from a.h:1,
from b.h:1,
< ... пропущено ... >
from a.h:1,
from b.h:1,
from a.h:1,
from b.h:1,
from a.h:1,
from c.cpp:1:
a.h:1:15: error: #include nested too deeply
#include "b.h"
^
\end{minted}
При наличии инклуд-guard'ы в хедерах {\bf a.h} и {\bf b.h} проявления ошибок являются более интересными. Рассмотрим на примере:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// a.h
#ifndef A_H
#define A_H
#include "b.h"
struct a
{
b* x;
};
#endif
// b.h
#ifndef B_H
#define B_H
#include "a.h"
struct b
{
a* x;
};
#endif
// c.cpp
#include "a.h"
\end{minted}
Компиляция {\bf c.cpp} приводит к ошибке:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
In file included from a.h:3:0,
from c.cpp:1:
b.h:7:5: error: ‘a’ does not name a type
a* x;
^
\end{minted}
Если же {\bf c.cpp} включает {\bf b.h} ошибка звучит:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
In file included from b.h:3:0,
from c.cpp:1:
a.h:7:5: error: ‘b’ does not name a type
b* x;
^
\end{minted}
Чтобы понять почему возникает подобная ошибка следует посмотреть на результат препроцессирования. В первом случае он такой:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
struct b
{
a* x;
};
struct a
{
b* x;
};
\end{minted}
Во втором такой:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
struct a
{
b* x;
};
struct b
{
a* x;
};
\end{minted}
Действительно, какую-бы структуру не объявляли первой, она не сможет сослаться на вторую поскольку вторая ещё не объявлена. В данном примере эти два хедера вообще не должны друг друга инклудить, поскольку достаточно объявления forward-деклараций:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// a.h
#ifndef A_H
#define A_H
struct b;
struct a
{
b* x;
};
#endif
// b.h
#ifndef B_H
#define B_H
struct a;
struct b
{
a* x;
};
#endif
\end{minted}
В данном примере какой бы из рекурсивно инклудящих друг-друга хедеров не проинклудить снаружи, мы получаем ошибку. В реальном коде встречаются рекурсивные инклуды, которые приводят к ошибке только если проиклудить один из них снаружи. Так везёт, что снаружи всегда инклудят какой-то один. Тем не менее это ошибка и рекурсивное включение следует избегать. В тех местах, где оно встретилось, как правило, от него можно избавиться использованием forward-деклараций, а так же разбитием большого хедера на части, с более аккуратным контролем зависимостей между частями.
\subsubsection{Защита от рекурсивного включения}
Возможно написать такой include-guard, который будет распозновать рекурсивное включение и выдавать ошибку об этом. Рассмотрим пример с рекурсивным включением из раздела \ref{recursive_include}, но в качестве include-guard будем использовать такой, который детектит рекурсивное включение:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// a.h
#ifndef A_H
#define A_H
#define INSIDE_A_H
#include "b.h"
struct a
{
b* x;
};
#undef INSIDE_A_H
#else
#ifdef INSIDE_A_H
#error recursive inclusion
#endif
#endif
// b.h
#ifndef B_H
#define B_H
#define INSIDE_B_H
#include "a.h"
struct b
{
a* x;
};
#undef INSIDE_B_H
#else
#ifdef INSIDE_B_H
#error recursive inclusion
#endif
#endif
// c.cpp
#include "a.h"
\end{minted}
Препроцессирование такого {\bf c.cpp} приведет к следующей ошибке:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
In file included from b.h:4:0,
from a.h:4,
from c.cpp:1:
a.h:14:2: error: #error recursive inclusion
#error recursive inclusion
^~~~~
\end{minted}
Как видно изнутри {\bf a.h} инклудится {\bf b.h}, который инклудит {\bf a.h}, который выдаёт ошибку, что он заинклужен рекурсивно.
Другим способом отловить рекуривное включение является опредение макроса в include-guard не до хедеров а после:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
// a.h
#ifndef A_H
#include "b.h"
struct a
{
b* x;
};
#define A_H
#endif
// b.h
#ifndef B_H
#include "a.h"
struct b
{
a* x;
};
#define B_H
#endif
// c.cpp
#include "a.h"
\end{minted}
\subsection{Проблемы при использовании препроцессора}
Основной сложностью\footnote{\url{http://www.stroustrup.com/bs_faq2.html\#macro} --- см. также FAQ Бьярна Страуструпа на тему, почему макросы это плохо.} при использовании макросов препроцессора является то, что препроцессор оперирует на уровне токенов, не зная ничего про контекст где макрос раскрывается. Предположим, что определен макрос {\bf errno}, а где-то ниже программист пытается определить локальную переменную {\bf errno}.
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define errno (*errno_location())
int process()
{
int errno = 0;
}
\end{minted}
Результатом препроцессирования этого фрагмента будет:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
int process()
{
int (*errno_location()) = 0;
}
\end{minted}
Как мы видим получившийся в результате препроцессирования фрагмент не объявляет переменную {\bf errno}. Этот фрагмент не объявляет вообще никакую переменную. В данном конкретном случае программист получит ошибку трансляции:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{text}
errno.cpp:5:17: error: function ‘int* errno_location()’ is initialized like a variable
int errno = 0;
^
\end{minted}
Сообщение об ошибке ссылается на некоторую функцию {\bf int* errno\_location()}, которая в пользовательском коде не упоминается. Такие ошибки могут быть запутывающими. При использовании библиотек, которые определяют много макросов с короткими именами это может доставлять неудобство, поскольку эти имена становиться невозможно использовать ни под что другое.
Второй проблемой при использовании пропроцессора является то, что отладчик ничего не знает про раскрытые макросы. Независимо от того, сколько кода пришло из макроса, отладчик будет работать как будто весь этот код написан в одной строке. Поэтому, как правило, в коде активно использующем макросы, сложно работать с отладчиком.
\subsection{Советы и лучшие практики}
\subsubsection{Предпочитайте альтернативы}
В связи с расширение языка, сейчас возможно использовать обычные языковые конструкции, там где раньше использовался препроцессор. Например, изначально препроцессор использовался для того, чтобы определять константы:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define BUFF_SIZE 10240
\end{minted}
Без использования препроцессора эту константу возможно объявить как:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
size_t const BUFF_SIZE = 10240;
\end{minted}
Аналогично function-like макросы часто можно заменить на inline-функции:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define STREQ(s1, s2) (strcmp((s1), (s2)) == 0)
\end{minted}
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
bool streq(char const* s1, char const* s2)
{
return strcmp(s1, s2) == 0;
}
\end{minted}
В случае когда типы аргументов могут быть различными возможно использование шаблонов:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
#define MIN(x, y) ((x) < (y) ? (x) : (y))
\end{minted}
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
template <class T>
T const& min(T const& x, T const& y)
{
return x < y ? x : y;
}
\end{minted}
\subsubsection{Своевременно undef-айнте макросы}
Если макрос предназначен лишь для использования внутри одной функции не забудьте его undef-айнуть по завершению этой функции:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
enum color
{
red,
green,
blue,
// ...
};
char const* color_name(color c)
{
switch (c)
{
#define CASE(name) case name: return #name
CASE(red);
CASE(green);
CASE(blue);
// ...
#undef CASE
}
}
\end{minted}
\subsubsection{Избегайте коротких имен}
Если необходимо объявить глобально доступный макрос, подберите ему такое имя, которое маловероятно приведет к колизии. {\bf GTK\_MAJOR\_VERSION} --- пример хорошего имени макроса. {\bf min}, {\bf check}, {\bf tmp}, {\bf out} --- примеры плохих имен.