写点什么

C++ 编译过程 宏 内联和静态变量

发布于: 2020 年 07 月 26 日
C++编译过程 宏 内联和静态变量

编译过程

C++程序的编译过程

GCC编译过程



  • 编写程序得到,X.cpp源文件。

  • 预处理(Processing)。系统利用预处理程序对程序中的预处理部分(以#开头的,放在函数体之外的,一般放在源文件前面,例如#include,#define等)进行处理,合理使用预处理可以方便程序的阅读、移植,并有利于模块化程序设计。

  • 编译(Compilation),得到.o目标文件

  • 链接(Linking),得到.out可执行文件



预处理指令

预定义宏

在标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.

__FILE__ //当前源文件的文件名,以字符串常量进行标准
__LINE__ //当前语句所在的行号, 以10进制整数标注.
__func__ //当前执行函数的函数名
__DATE__ //程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.
__TIME__ //程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.

gcc定义的预定义:

__OPTMIZE__ //如果编译过程中使用了优化, 那么该宏被定义为1.
__VERSION__ //显示所用gcc的版本号.

想要查看更多gcc支持的预定义可以执行:

cpp -dM /dev/null

修改预定义__LINE__和__FILE__值

// 修改__LINE__和__FILE__
#line <行号> <文件名>

例如:

// precessing.cpp
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << __FILE__ << " " << __LINE__ << std::endl;
#line 100 "NewLineDefinition" //修改__LINE__和__FILE__
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;
}



编译阶段

宏定义是在预编译阶段通过字符串替换的方式进行实现的:

//compile_main.cpp
#include <iostream>
#define PI 3.1415926
int main(int argc, char* argv[]) {
std::cout << PI << std::endl;
return 0;
}

执行下述指令,只激活预编译:

g++ -E compile_main.cpp

忽略一些和宏定义不相关的信息可以看到:

# 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__
//#undef PI
#define PI 3.2
#endif



#ifndef __B_H__
#define __B_H__
//#undef PI
#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来取消宏定义,之后再进行宏定义,此时编译便可以成功编译,此时执行结果是:

3.14159

如果将main函数中的头文件位置进行调换,更改为如下形式:

#include "B.h"
#include "A.h"
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << PI << std::endl;
return 0;
}

则执行的结果是

3.2

有时候,我们的工程可能会包含多个第三方的库,相同的宏被给予不同的值,该如何使用?像上面的形式一样,再宏定义前取消宏定义,之后再进行宏定义即可,可能需要注意的是如果你的某个文件同时包含了这两个两个宏定义的文件,书写的顺序就很重要了,且看下面的例子:

//A.h
#ifndef __A_H__
#define __A_H__
#undef PI
#define PI 3.2
void printPI_A(void);
#endif



//A.cpp
#include "A.h"
#include <iostream>
void printPI_A(void) {
std::cout << PI << std::endl;
}



//B.h
#ifndef __B_H__
#define __B_H__
#undef PI
#define PI 3.1415926
void printPI_B(void);
#endif



//B.cpp
#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

运行得到结果:

3.14159
3.2
3.14159

可以看到,在A.cpp中显示的是A.h中定义的PI的值,而B.cpp中显示的是B.h中定义的PI值。

宏定义适用性

应用场景

  • 头文件的内容都放在 #ifndef 和 #endif 中,防止该头文件被重复引用。

缺陷

  • 宏在预编译阶段进行文本替换,无法进行类型检查,同时会导致代码膨胀。

  • 无法进行单步调试。

  • 由于优先级的不同,可能会存在副作用。

  • 在C++中宏无法操作私有成员。



静态(static)变量

定义在头文件中的static变量在预编译阶段会随着头文件复制到调用的.cpp文件中,示例如下:

//header1/header1.h
#ifndef __HEADER1__H__
#define __HEADER1__H__
#include <iostream>
static int uintt8max = 0xFF;
static int uint16max;
void getTimes(void) {
static int callTimes = 0;
++callTimes;
}
#endif



//main.cpp
#include "header1/header1.h"
int main(int argc, char* argv[]) {
return 0;
}

执行下面的命令只激活预处理阶段:

g++ -E main.cpp

忽略和自定义的头文件不相关的实现,可以看到如下的结果:

# 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文件中,那么如果有两个头文件同时定义了命名相同的变量便会导致出现重定义的问题,看下面的示例:

//header2/header2.h
#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;
}

运行结果如下:

IN main
254
0



概念区分

宏定义和typedef

宏定义和typedef都是对变量另起一个别名,两者存在如下的差异:

  • #define是C语言自定义的预处理指令,在预编译阶段进行简单的字符串替换,不作类型检查;typedef是关键字,在编译阶段进行处理,作类型检查,它只是用来给定义的类型取个别名。

  • 作用域不同,#define没有作用域限制,而typedef有作用域。



宏定义和内联函数

  • 宏定义是在预处理阶段进行代码替换,而内联函数是在编译阶段插入代码。

  • 宏定义没有类型检查,而内联函数有类型检查。

参考资料

发布于: 2020 年 07 月 26 日阅读数: 77
用户头像

正向成长 2018.08.06 加入

想要坚定地做大规模数据处理(流数据方向),希望结合结合批处理的传统处理方式,以及之后流批混合处理方向进行学习和记录。

评论

发布
暂无评论
C++编译过程 宏 内联和静态变量