1. 先来看段代码
//file1.cpp
#include <iostream>
using std::cout;
int x=1;
void f(){
cout<<x;
}
//file2.cpp
void f(); // extern void f(); the same.
int main(int argc, char const *argv[])
{
f();
return 0;
}
如果单独编译两个文件,能够得到结果。
如果,再把编译得到的两个object链接,也是没有问题的。
对于file2.cpp,如果按照我以前的想法,我觉得这里为什么不需要include 一个header呢?
2. compilation & Linkage
先说编译。
我们的编译器,只读取一个文件和它的header作为输入!!
然后进行预处理,预处理之后的结果叫做一个translation unit.
比如,我们来看编译file1.cpp的预处理。
会读到include了一个header。
然后,编译器会把header里面的内容,放在file1.cpp里面的文件内容的前面。
变成了一个新的文件。
但是,注意,这可能是一个recursive的过程。
因为在这个被include的header里面,可能还include了其他header。
那么,又要进行一次展开。
最后,会得到一个很长,很长的文件。
然后,编译器还要对一些macros进行替换。
最后替换结束后,得到的文件,就叫做translation unit。
现在假如来编译file2.cpp,预处理的阶段什么都不需要做。
没有include header,也没有任何macros。
那么也得到了一个translation unit。
接下来,我们要做的事情是开始对translation unit开始编译了。
编译是对每一个translation unit单独进行的。
编译的时候,编译器只看当前正在编译的translation unit里面的内容。
现在来看下编译file2.cpp的过程:
编译器,读到了一个函数的声明。
注意,在这里这个函数是在namespace scope(file scope)。
那么,它会被默认为external linkage.
意思是这个函数的可以被不同的translation unit里面的代码使用。
那么,这个函数的定义,可能在file2.cpp里面,也可能在其他,一会会一起链接的文件里面。
再往下面走,就没有什么特别了。
同理。
在file1.cpp里面。
x和f都有external linkage。
也因此,我们能够在file2.cpp的translation unit使用f。
但是,如果我们要在file2.cpp里面,使用x,该怎么办呢?
因为int的声明和定义是一起的。
如果,我们一旦有了声明,那么我们实际上在file2.cpp里面就重新定义了x。
所以,如果要在file2.cpp里面使用x,那么需要做的是:
extern int x;
实际上,我们在file2.cpp里面,声明函数f,也是可以加上extern的:
extern void f();
只是,因为它们在namespace scope里面,默认了是extern linkage的。
不需要特别指定。
另外,也注意到,如果我们要有一个file1.hpp
那么这个file1.hpp里面要有什么内容呢?
其实就很好想了:
//file1.hpp
void f();
extern int x;
于是,file2.cpp就可以写成:
//file2.cpp
#include "file1.hpp"
int main(int argc, char const *argv[])
{
f();
return 0;
}
那么,编译器在编译的时候,会把file1.hpp的内容,在file2.cpp中展开,得到file2对应的translation unit。
顺便提一下,f()对于c++和c来说是不同的。
一个是没有arguments,一个是可以有数量不限的arguments
ref: the c++ programming language 9.1&9.2
一个是没有arguments,一个是可以有数量不限的arguments
ref: the c++ programming language 9.1&9.2