- Expert C++
- Vardan Grigoryan Shunguang Wu
- 603字
- 2021-06-24 16:33:53
Understanding preprocessing
A preprocessor is intended to process source files to make them ready for compilation. A preprocessor works with preprocessor directives, such as #define, #include, and so on. Directives don't represent program statements, but they are commands for the preprocessor, telling it what to do with the text of the source file. The compiler cannot recognize those directives, so whenever you use preprocessor directives in your code, the preprocessor resolves them accordingly before the actual compilation of the code begins. For example, the following code will be changed before the compiler starts to compile it:
#define NUMBER 41
int main() {
int a = NUMBER + 1;
return 0;
}
Everything that is defined using the #define directive is called a macro. After preprocessing, the compiler gets the transformed source in this form:
int main() {
int a = 41 + 1;
return 0;
}
As already mentioned, the preprocessor is just processing the text and does not care about language rules or its syntax. Using preprocessor directives, especially macro definitions, as in the previous example, #define NUMBER 41 is error-prone, unless you realize that the preprocessor simply replaces any occurrence of NUMBER with 41 without interpreting 41 as an integer. For the preprocessor, the following lines are both valid:
int b = NUMBER + 1;
struct T {}; // user-defined type
T t = NUMBER; // preprocessed successfully, but compile error
This produces the following code:
int b = 41 + 1
struct T {};
T t = 41; // error line
When the compiler starts compilation, it finds the assignment t = 41 erroneous because there is no viable conversion from 'int' to 'T'.
It is even dangerous to use macros that are correct syntactically but have logical errors:
#define DOUBLE_IT(arg) (arg * arg)
The preprocessor will replace any occurrence of DOUBLE_IT(arg) with (arg * arg), therefore the following code will output 16:
int st = DOUBLE_IT(4);
std::cout << st;
The compiler will receive this code as follows:
int st = (4 * 4);
std::cout << st;
Problems arise when we use complex expressions as a macro argument:
int bad_result = DOUBLE_IT(4 + 1);
std::cout << bad_result;
Intuitively, this code will produce 25, but the truth is that the preprocessor doesn't do anything but text processing, and in this case, it replaces the macro like this:
int bad_result = (4 + 1 * 4 + 1);
std::cout << bad_result;
This outputs 9, and 9 is obviously not 25.
To fix the macro definition, surround the macro argument with additional parentheses:
#define DOUBLE_IT(arg) ((arg) * (arg))
Now the expression will take this form:
int bad_result = ((4 + 1) * (4 + 1));
It is strongly suggested to use const declarations instead of macro definitions wherever applicable.
The same preceding example would be type-checked and processed at compile time if we used a constexpr function:
constexpr int double_it(int arg) { return arg * arg; }
int bad_result = double_it(4 + 1);
Use the constexpr specifier to make it possible to evaluate the return value of the function (or the value of a variable) at compile time. The example with the NUMBER definition would be better rewritten using a const variable:
const int NUMBER = 41;
- Python概率統計
- Learning ROS for Robotics Programming(Second Edition)
- Docker and Kubernetes for Java Developers
- C語言程序設計案例教程(第2版)
- Dynamics 365 Application Development
- 垃圾回收的算法與實現
- 數據結構習題精解(C語言實現+微課視頻)
- C#程序設計(項目教學版)
- Emgu CV Essentials
- 軟件測試綜合技術
- JavaScript+jQuery網頁特效設計任務驅動教程
- SQL Server 入門很輕松(微課超值版)
- Python數據可視化之美:專業圖表繪制指南(全彩)
- Java 9 Programming By Example
- ROS機器人編程實戰