提出问题
第一次遇到#pragma once的时候,到网上找了些资料,大部分答案都大同小异,大概意思是让编译器只编译一次。我们都知道,一般不在头文件中定义全局变量,因为那会导致该变量多次定义。那么问题来了,既然用#pragma once预编译命令可以防止重复编译,为什么不能在头文件中定义全局变量呢?这个问题长期困扰着我,平时也就是记住这些规则而已。正所谓要知其所以然嘛,那我们就来探讨一下这个问题。
#pragma once的作用
我们从三个文件开始。
第一个文件是test1.h
//#pragma once
//test1.h
struct test1
{
int val;
};
第二个文件是test2.h
#pragma once
//test2.h
#include "test1.h"
最后一个文件test.cpp
// test.cpp
#include "stdafx.h" //我用的是vs2015,其他编译器可以不用包含这个头文件.
#include "test1.h"
#include "test2.h"
int main()
{
return 0;
}
这种情况是可能的,因为我们可能会同时在源文件中使用这两个头文件的内容。
下面我们来编译一下:
为什么会报错呢?一个源文件包含一个头文件,只是以头文件的形式展开,就是把头文件里的内容copy到源文件中去。在test.cpp文件中,#include "test2.h"展开后,会包含一个test1.h,而原本就包含了一个test1.h。最终结果就是结构体test1被定义了两次,然后报错了。
那么我把test1.h的注释(//#pragma once)去掉会怎么样?答案是yes!编译通过。#pragma once的作用是对于某一个文件,如果多次包含另一个文件的话,只编译一次。
头文件定义全局变量
我们继续创建3个文件。
第一个文件为test1.h
#pragma once
//test1.h
int a = 0;
第二个文件为test1.cpp
//test1.cpp
#include "stdafx.h"
#include "test1.h"
第三个文件为test.cpp
// test.cpp
#include "stdafx.h" //我用的是vs2015,其他编译器可以不用包含这个头文件.
#include "test1.h"
int main()
{
return 0;
}
编译看下会发生什么。
oh no!不是说好的用#pragma once只编译一次吗,怎么又重定义了呢?宝贝你居然骗我。我们仔细观察错误提示发现,LNKxxx之类的。这是说明在编译的过程中,链接出错了。(?a@@3HA)是编译器对全局变量a生成的一个全局符号,这个符号的作用相当于链接器识别的一个id。
而test1.cpp和test.cpp两个源文件都包含了test1.h。因此int a = 0;会被编译器展开到那两个源文件中,因此,也会生成两个符号。在链接的过程中,这两个符号重复了,所以就报错了。
如果有同学对编译和链接的过程不是很明白的,可以参考深入理解计算机系统的第七章,那里讲的很透彻。
全局变量的解决方案
针对上一部分的情况,那么我想要在test1.cpp和test.cpp两个源文件中使用全局变量a怎么办?
我们修改这3个文件
test1.h:
#pragma once
//test1.h
extern int a;
int func();
test.cpp:
// test.cpp
#include "stdafx.h" //我用的是vs2015,其他编译器可以不用包含这个头文件.
#include "test1.h"
int a = 0;
int main()
{
a = 1;
printf("%d\n", a);
func();
printf("%d\n", a);
return 0;
}
test1.cpp:
//test1.cpp
#include "stdafx.h"
#include "test1.h"
int func() {
a = 2;
return a;
}
在test1.h中,使用extern表示变量a是外来的,在这里只是声明它,并没有定义。这样也就不会生成两个全局符号。
以下是运行结果,可以看到test1.cpp中的func函数成功地修改了变量a的值。
总结
#pragma once预编译指令防止重复编译是针对某一个源文件而言的,即某个源文件包含了两个头文件,而那两个头文件之一又包含了另一个,此时#pragma once会发生作用,起到只编译一次的作用。
全局变量不要定义在头文件中则适用于多个源文件的。多个源文件同时包含某一个头文件,那个头文件又定义了一个全局变量。此时会发生链接错误,解决方法是使用extern。
这些细节很容易被我们忽略掉,从而引起混淆。写篇文章mark一下。