能不能在头文件中定义全局变量?
编译器驱动程序
大多数编译系统提供编译器驱动程序(compiler driver),它代表用户在需要时调用语言预处理器、编译器、汇编器、和链接器。
我们所常说的 “编译生成可执行文件” 实际包括以下过程:
-
预处理器 (某些编译系统,预处理器被集成到 编译器 中)
cpp [other arguments] main.c /tmp/main.i
处理预处理指令,生成中间文件,所有的预处理器命令都是以井号(#)开头。主要任务包括:
- 删除注释;
- 插入被 #include 指令所包含的的文件内容;
- 定义和替换由#define指令定义的符号;
- 确定代码的部分内容是否应该根据一些条件编译指令进行编译;
-
编译器
cc1 /tmp/main.i -Og [other arguments] -o /tmp/main.s
将预处理后的中间文件翻译成一个ASCII汇编语言文件。
-
汇编器
as [other arguments] -o /tmp/main.o /tmp/main.s
将ASCII汇编语言文件翻译成一个可重定位目标文件。
-
链接器
ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o
将一个或多个可重定位目标文件,以及必要的系统目标文件组合起来,创建一个可执行文件。
![编译生成可执行文件过程.png](https://i.loli.net/2020/10/10/9y28jwNmZRcgiGE.png)
非静态全局变量
先个下结论:可以,但非常非常非常不建议!!
- 若头文件仅被一个源文件使用到,可以正常生成可执行文件。
- 若头文件被多个源文件包含,可正常执行完 cpp、cc1、as,但在链接(ld)时便会报错(重复定义)。
由上面的图可以分析出,源程序代码在生成可执行文件的过程中,前三步均可看做独立完成的,仅在最后一步将多个源文件生成的目标文件链接起来。
如下代码:
a.cpp 、 b.cpp 在经过预处理器、编译器、汇编器时均认为是没有定义变量 A 的,于是都有变量定义。在链接时,便出现了二义性(重复定义)。
[root@localhost val]# g++ a.cpp b.cpp
/tmp/ccVzRqVG.o:(.bss+0x0): multiple definition of `A'
/tmp/ccjI4zgp.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status
// a.h
#ifndef A_H
#define A_H
int A;
void funcA();
#endif
// a.cpp
#include<iostream>
#include "a.h"
void funcA(){
A = 10;
std::cout << "a.cpp : " << A << std::endl;
}
// b.cpp
#include<iostream>
#include "a.h"
int main(){
funcA();
A =20;
std::cout << "b.cpp : " << A << std::endl;
return 0;
}
静态全局变量
上面的代码如若将变量 A定义为 static,编译执行没有问题。但是,静态变量的作用域仅在 ”当前源文件“ ,即两处的变量 A 不是同一个变量,是不同的文件作用域内的静态变量。
“静态全局变量” 这个称谓其实就有点怪异,静态变量在文件作用域内就是全局的,且仅在文件作用域内。
使用建议
全局变量在某一个源文件中定义,其余源文件若要使用,将外部声明 extern
写在头文件中,源文件包含这个头文件。如下:
// a.cpp
int nums; // 全局变量定义
// out.h
extern int nums; //外部变量声明
// b.cpp
#include "out.h" // 相当于声明了外部变量
void set_nums(int val){
nums = val;
}
g++ a.cpp b.cpp
更加深入-全局变量
如果在 a.h 中变量定义时,定义为 “弱定义”,那么是能达到预期的目标。
int A __attribute__((weak));
Linux gcc
注意:仅在 gcc 下正确,换做 g++ 同样报错(重复定义)。
在编译时,编译器向汇编器输出每个全局符号,或者是强(strong)或者是弱(weak),而编译器把这个信息隐含地编码在可重定位目标文件的符号表里。函数和已初始化的全局变量是强(strong)符号 。未初始化的全局变量是弱(weak)符号。
根据弱符号的定义,Linux链接器使用下面的规则来处理多重定义的符号名:
- 规则1:不允许有多个同名的强符号。
- 规则2:如果有一个强符号和多个弱符号同名,那么选择强符号。
- 规则3:若干有多个弱符号同名,那么从这些弱符号中任意选择一个。
// bar.c
#include<stdio.h>
int x;
void f(){
// std::cout << "begin f() x = " << x << std::endl;
printf("begin f() x = %d\n", x);
x = 654321;
}
// foo.c
#include<stdio.h>
int x = 12345; // 已初始化 强符号
void f();
int main(){
f();
// std::cout << "after f() x = " << x << std::endl;
printf("after f() x = %d\n", x);
return 0;
}
[root@localhost 7]# gcc foo.c bar.c
[root@localhost 7]# ./a.out
begin f() x = 12345
after f() x = 654321