一直对编译这个事情不是太明白,想好好学习一下,今天自己写东西试验,遇到了这么个问题,想请教一下,先贴代码
// test.h
#ifndef JUST_FOR_TEST_H_
#define JUST_FOR_TEST_H_
struct Test {
static int i;
void t();
};
int Test::i = 0;
#endif
// test.cpp
#include "test.h"
void Test::t() { }
// main.cpp
#include "test.h"
int main() {
Test t;
t.t();
}
本来觉得这个是很简单的,但是链接的时候报了错,提示是
ld: 1 duplicate symbol for architecture x86_64
将test.h中静态成员的定义放到cpp文件中就好了,但是我很不理解,不是使用了头文件保护吗?这样不是已经确保了一个头文件在一个工程中只会被包含一次吗?是我哪里的理解出了偏差了吗?希望各位高手可以指点一下,本人菜鸡,不是学计算机出身的,所以问的问题可能很傻,如果可以的话希望可以讲的详细一点,如果可以介绍两本学习的书就更好了,先谢过了!
短答案:
headguard只能防止头文件被二次包含。你编译时的问题是,i在不同的编译单元中被定义。
多数ide编译时会将所有的源文件分别编译成目标文件,最后由连接器将目标文件连接起来。目标文件之间的连接关系是由对应源文件中的include来推断的。头文件不参与编译。此处IDE生成的编译脚本差不多是
clang++ main.cpp test.cpp
。当然除此之外还有很多工程设置里的编译选项。你的例子中,编译器会编译test.cpp和main.cpp,产生目标文件。然后连接器会将这两个目标文件连接起来。由于静态成员变量i定义在头文件中,并且两个源文件都包含了这个头文件。结果就是i在两个编译单元中分别被定义了一次。最后连接时就会出现连接错误。
长答案:
你遇到的链接错误实际上是由于违背ODR引起的。
ODR约定:
但除此之外还有很多的特例。在这里就不列举了(主要因为是我列不全……)。
其中有三个概念很重要:translation unit, definition和program.
translation unit是编译单元,一共有几个编译单元由编译指令决定。编译单元的编译结果称为目标文件。definition是定义,在这个例子中
int Test::i = 0;
是静态成员变量i
的定义,根据ODR,这个定义在整个程序中只能出现一次。program是程序,程序由众多的目标文件链接而成。编译指令:
clang++ main.cpp test.cpp -o main
此次编译一共有两个编译单元main.cpp和test.cpp。他们会被分别编译成目标文件。
#include
是预编译宏,他会引入对应文件的内容,并且所有的预编译指令都会在源代码被编译前执行。也就是说编译单元"test.cpp"的内容(或者说clang编译器收到的代码)为:编译单元"main.cpp"的内容为:
所有的宏都执行完毕,注释会被编译器忽略。
这两个编译单元会被分别编译成目标文件,比方说test.o和main.o。编译工作结束。然后编译器会将这两个目标文件test.o和main.o链接成一个程序,违背ODR就是在这个链接时发生的。
如果
int Test::i = 0;
的定义在test.cpp中。那么在main.cpp中就不会出现这个定义,也就没有违背ODR。