编译过程
C++程序的编译过程
GCC编译过程
预处理指令
预定义宏
在标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.
__FILE__
__LINE__
__func__
__DATE__
__TIME__
gcc定义的预定义:
想要查看更多gcc支持的预定义可以执行:
修改预定义__LINE__和__FILE__值
例如:
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << __FILE__ << " " << __LINE__ << std::endl;
#line 100 "NewLineDefinition"
std::cout << __LINE__ << " " << __FILE__ << std::endl;
std::cout << __FILE__ << " " << __LINE__ << std::endl;
return 0;
}
运行结果:
precessing.cpp 16
100 NewLineDefinition
NewLineDefinition 101
#define使用方法
常用的define宏定义的格式:
#define 宏名称 替换信息 //添加宏定义
#undef 宏名称 //取消宏定义
//测试某个宏是否被定义
#ifdef 宏名称
#ifndef 宏名称
#if defined(<宏名称>)
宏有两种使用方式:Object-like宏,它适用于Object对象;Fuction-like宏适用于函数方法。
Object-like宏
Object-like 类型宏就像普通的数据对象,多用于数字常量的情形且宏名一般使用全大写形式。
#define BUFSIZE 1024
#define TABLESIZE BUFSIZE
Function-like 宏
定义像函数一样的宏。例如:
#define MIN(x, y) ((x) > (y) ? (y) : (x))
拼接
#include <iostream>
# 将表达式转化为字符串
## 将两个表达式粘合为一个字符串
*/
#define SUM(a, b) std::cout << #a << " + " << #b << " = " << (a) + (b) << std::endl;
#define NAME(n) num ## n
int main(int argc, char* argv[]) {
SUM(16, 18);
int num0 = 10;
std::cout << NAME(0) << std::endl;
return 0;
}
编译阶段
宏定义是在预编译阶段通过字符串替换的方式进行实现的:
#include <iostream>
#define PI 3.1415926
int main(int argc, char* argv[]) {
std::cout << PI << std::endl;
return 0;
}
执行下述指令,只激活预编译:
忽略一些和宏定义不相关的信息可以看到:
# 4 "compile_main.cpp"
int main(int argc, char* argv[]) {
std::cout << 3.1415926 << std::endl;
return 0;
}
展开顺序
宏的展开是在处理源码时按照其出现位置进行的,如果宏定义有嵌套关系,则层层进行展开。宏的展开顺序不是按照宏的定义顺序进行的。
#include <iostream>
#define GREETING_NAME "wayou"
#define GREETING "hello," GREETING_NAME
int main() {
std::cout << GREETING << std::endl;
return 0;
}
即使调换第2行和第3行的顺序,激活预处理阶段,得到的相同的如下结果:
int main() {
std::cout << "hello," "wayou" << std::endl;
return 0;
}
宏redefine问题
C++宏定义,如果重复定义一般不报错会给出Warning,编译期会采取最后的定义给出数值,但是如果在编译选项中添加了-Werror,此时系统会给出:
error: XXXX redefined [-Werror]
例如,下面的实现
#ifndef __A_H__
#define __A_H__
#define PI 3.2
#endif
#ifndef __B_H__
#define __B_H__
#define PI 3.1415926
#endif
#include "A.h"
#include "B.h"
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << PI << std::endl;
return 0;
}
执行编译命令:
g++ -o compile_main compile_main.cpp -Werror
此时会报
B.h:3:0: error: "PI" redefined [-Werror]
#define PI 3.1415926
^
In file included from compile_main.cpp:1:0:
A.h:3:0: note: this is the location of the previous definition
#define PI 3.2
这是我们可以在PI的宏定义前添加#undef PI来取消宏定义,之后再进行宏定义,此时编译便可以成功编译,此时执行结果是:
如果将main函数中的头文件位置进行调换,更改为如下形式:
#include "B.h"
#include "A.h"
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << PI << std::endl;
return 0;
}
则执行的结果是
有时候,我们的工程可能会包含多个第三方的库,相同的宏被给予不同的值,该如何使用?像上面的形式一样,再宏定义前取消宏定义,之后再进行宏定义即可,可能需要注意的是如果你的某个文件同时包含了这两个两个宏定义的文件,书写的顺序就很重要了,且看下面的例子:
#ifndef __A_H__
#define __A_H__
#undef PI
#define PI 3.2
void printPI_A(void);
#endif
#include "A.h"
#include <iostream>
void printPI_A(void) {
std::cout << PI << std::endl;
}
#ifndef __B_H__
#define __B_H__
#undef PI
#define PI 3.1415926
void printPI_B(void);
#endif
#include "B.h"
#include <iostream>
void printPI_B(void) {
std::cout << PI << std::endl;
}
#include "A.h"
#include "B.h"
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << PI << std::endl;
printPI_A();
printPI_B();
return 0;
}
编译
g++ -c A.cpp
g++ -c B.cpp
g++ -o compile_main compile_main.cpp -Werror A.o B.o
运行得到结果:
可以看到,在A.cpp中显示的是A.h中定义的PI的值,而B.cpp中显示的是B.h中定义的PI值。
宏定义适用性
应用场景
缺陷
静态(static)变量
定义在头文件中的static变量在预编译阶段会随着头文件复制到调用的.cpp文件中,示例如下:
#ifndef __HEADER1__H__
#define __HEADER1__H__
#include <iostream>
static int uintt8max = 0xFF;
static int uint16max;
void getTimes(void) {
static int callTimes = 0;
++callTimes;
}
#endif
#include "header1/header1.h"
int main(int argc, char* argv[]) {
return 0;
}
执行下面的命令只激活预处理阶段:
忽略和自定义的头文件不相关的实现,可以看到如下的结果:
# 4 "header1/header1.h" 2
# 4 "header1/header1.h"
static int uintt8max = 0xFF;
static int uint16max;
void getTimes(void) {
static int callTimes = 0;
++callTimes;
}
# 2 "main.cpp" 2
int main(int argc, char* argv[]) {
return 0;
}
可见,预处理阶段C++会将头文件的相关定义复制到包含其的cpp文件中,那么如果有两个头文件同时定义了命名相同的变量便会导致出现重定义的问题,看下面的示例:
#ifndef __HEADER2__H__
#define __HEADER2__H__
#include <iostream>
static int uintt8max = 0xfE;
#endif
#include "header1/header1.h"
#include "header2/header2.h"
int main(int argc, char* argv[]) {
return 0;
}
编译便会出现:
header2/header2.h:4:12: error: redefinition of ‘int uintt8max’
static int uintt8max = 0xfE;
通过上面的激活预处理阶段可以看到,重定义是由于在进行头文件复制导致的,那么我们百年可以通过将变量定义在不同作用域的形式,在需要调用的作用域内包含相关的头文件来避免上述问题,实现形式如下:
#include "header1/header1.h"
int main(int argc, char* argv[]) {
std::cout << "IN main " << std::endl;
{
#include "header2/header2.h"
std::cout << uintt8max << std::endl;
}
std::cout << uint16max << std::endl;
return 0;
}
运行结果如下:
概念区分
宏定义和typedef
宏定义和typedef都是对变量另起一个别名,两者存在如下的差异:
宏定义和内联函数
参考资料
评论