This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.
我们开学到现在一直在学C++, 用的编译器是VS2017, 昨天作业里面有一道题让使用 cmath 中定义的宏 M_PI 去计算圆的周长。
任务很简单, 于是写下代码:
1 |
|
可是还没见运行, VS 的红色下划线就大大的画在了 M_PI
这个宏底下, 鼠标移上去一看是个undefined
, 当时我知道肯定是还要做点别的工作, 上手去Google一搜, 原来是使用 cmath
中定义的非标准常量需要定义_USE_MATH_DEFINES
宏, OK, 代码变成了如下模样:
1 |
|
以为 VS 日常卡顿得我慢慢的失去了信心 - 很奇怪, 还是有undefined错误
于是上StackOverflow查了查, 查到了这样的一个帖子:
M_PI works with math.h but not with cmath in Visual Studio
最后题主自己解决了问题, 说是把 #define _USE_MATH_DEFINES
移到首行就好了
当时我就觉得怎么有这么玄学的事情, 但是, 世间玄学的事情还真的多, 这次 VS 没有日常卡顿, 红标直接消失
虽说问题解决了, 但是我很好奇是什么原因导致的如此玄学的问题, 找了一遍 Google , 也没找到有详细说这个问题的
我当时想, 既然要移动到 #include iostream
之前, 那么肯定是 iostream
的引用导致的这个问题, 可是标准输入输出库会有什么问题呢?
我打开了 iostream
文件, 并没有发现关于数学常量的宏定义, 于是改变思路, 先去寻找定义这个 M_PI
宏的文件, 从 cmath
出发, 经过一番寻找终于找到了 M_PI
宏定义和条件编译 #define _USE_MATH_DEFINES
的位置,分别在:
1 | // cmath -> stdlib -> math.h, line 13: |
以及包含的 correcrt_math_defines.h
:
1 | // corecrt_math_defines.h, line 22: |
找到定义和条件编译选项的宏之后, 我开始分析预编译器的引用过程: 我首先打开了 iostream
, 在里面只有一个引用 istream
于是打开它接着逐级寻找所引用包含的文件, 终于在这样的包含之中发现了线索:iostream -> istream -> ostream -> ios -> xlocnum
在最后一级的 xlocnum
中包含了 cmath
, 这一刻我终于明白了为什么要把条件编译的开关定义在引用 iostream
之前:
如果将宏定义放在引用输入输出库之后, 预编译器大概是这样工作的:
- 首先, 预编译器按照首行的指令引用
iostream
头文件 - 然后预编译器根据引用逐级的扩展文件, 最终到了
xlocnum
头文件 - 预编译器根据其中的指令去拓展
cmath
头文件 - 又是一番寻找加载符号, 从
cmath
找到了math.h
- 注意, 这个时候我们还没有定义
_USE_MATH_DEFINES
编译器便没有加载corecrt_math_defines.h
中的符号 - 等到以上的
iostream
加载完成之后, 预编译器定义了宏_USE_MATH_DEFINES
- 根据第三行的指令, 预编译器试图加载
cmath
但是发现其实自己已经加载过了, 于是跳过了
于是最终的结果是条件编译选项并没有被执行, 即使我们定义了宏 _USE_MATH_DEFINES
为了验证我的想法, 我使用编译 /P
选项将预编译结果写到一个 .i
文件里:
1 | #include <iostream> |
查看预编译的结果, 在其中找到了这样的信息:
1 | #line 1368 "c:\\program files (x86)\\windows kits\\10\\include\\10.0.17134.0\\ucrt\\stdlib.h" |
可以看到, 预编译器在一路加载的过程中遇到了 stdlib.h
并从这里跳转到了 math.h
由于 math.h
的首行定义是引用 correc_math.h
预编译器开始加载其中的符号, 但是根据对整份文件的搜索(结果文件太大了, 有60000行之多)
从这里跳转出去之后并没有再回到 math.h
而是开始加载其余的内容和展开函数之类的工作, 因为整个 math.h
的内容只有一个引用和条件编译对于 corecrt_math_defines.h
的引用, 所以可以知道这个 _USE_MATH_DEFINES
的常量定义并没有生效.
而当我搜索所有关于源文件代码 source.cpp
的预编译结果时只有两个:
1 | #line 1 "c:\\users\\guiqiqi\\source\\repos\\testforprecompiler\\source.cpp" |
这里验证了, 第二行的 cmath
加载由于在加载 iostream
的过程中已经完成, 预编译器直接对这一行进行了忽略(甚至在源文件中定义 _USE_MATH_DEFINES
也一并被忽略了…)
不得不感叹一句, 现代的编译器是真的太智能了。
Comments