Heim > Fragen und Antworten > Hauptteil
一直对编译这个事情不是太明白,想好好学习一下,今天自己写东西试验,遇到了这么个问题,想请教一下,先贴代码
// 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文件中就好了,但是我很不理解,不是使用了头文件保护吗?这样不是已经确保了一个头文件在一个工程中只会被包含一次吗?是我哪里的理解出了偏差了吗?希望各位高手可以指点一下,本人菜鸡,不是学计算机出身的,所以问的问题可能很傻,如果可以的话希望可以讲的详细一点,如果可以介绍两本学习的书就更好了,先谢过了!
巴扎黑2017-04-17 15:41:16
headguard只能防止头文件被二次包含。你编译时的问题是,i在不同的编译单元中被定义。
多数ide编译时会将所有的源文件分别编译成目标文件,最后由连接器将目标文件连接起来。目标文件之间的连接关系是由对应源文件中的include来推断的。头文件不参与编译。此处IDE生成的编译脚本差不多是clang++ main.cpp test.cpp
。当然除此之外还有很多工程设置里的编译选项。
你的例子中,编译器会编译test.cpp和main.cpp,产生目标文件。然后连接器会将这两个目标文件连接起来。由于静态成员变量i定义在头文件中,并且两个源文件都包含了这个头文件。结果就是i在两个编译单元中分别被定义了一次。最后连接时就会出现连接错误。
你遇到的链接错误实际上是由于违背ODR引起的。
ODR约定:
Only one definition of any variable, function, class type, enumeration type, or template is allowed in any one translation unit (some of these may have multiple declarations, but only one definition is allowed).
One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries).
但除此之外还有很多的特例。在这里就不列举了(主要因为是我列不全……)。
其中有三个概念很重要: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编译器收到的代码)为:
// test.cpp
// test.h
struct Test {
static int i;
void t();
};
int Test::i = 0;
void Test::t() { }
编译单元"main.cpp"的内容为:
// main.cpp
// test.h
struct Test {
static int i;
void t();
};
int Test::i = 0;
int main() {
Test t;
t.t();
}
所有的宏都执行完毕,注释会被编译器忽略。
这两个编译单元会被分别编译成目标文件,比方说test.o和main.o。编译工作结束。然后编译器会将这两个目标文件test.o和main.o链接成一个程序,违背ODR就是在这个链接时发生的。
如果int Test::i = 0;
的定义在test.cpp中。那么在main.cpp中就不会出现这个定义,也就没有违背ODR。