c/c++程序编译时是对每个编译单元单独进行编译, 生成.o目标文件, 最后由连接器对所有的.o文件进行链接生成最后的可执行程序。这里的编译单元是指.c, .cc, 和cpp文件。 每个编译单元是相互独立编译的,而且互不影响的. 当编译器对编译单元编译时, 会生成包含变量和函数定义, 地址的.o文件, 比如, 有一个src1.cpp,
int global = 1;
int func() {
return global;
}
当编译器对src1.cpp编译时, 会生成一个包含global和func名称, 地址的一个表, 叫做导出表。再有第二个源文件src2.cpp
extern int global;
int func2() {
return ++global;
}
这里用extern来声明global, 告诉编译器global是在其他编译单元中定义的, 这个时候编译器就不会再对global进行定义(也就是说, 不会把global放在生成的导出表里面 如果不用extern, 就会在链接的时候出现多次定义的错误), 但也要记录global的一些信息, 此时会将global放在一个未解决符号表(unresolvedsymbol table)表里面, 然后会在导出表里面记录func2的地址和信息。最后链接器对所有的.o目标文件进行链接, 如果发现某个目标文件有未解决符号表, 就会去其他的.o目标文件里面查找这个符号的定义, 如果是系统的库, 就回去系统的库文件查找, 然后替换他. 如果没有找到定义, 就会出现链接错误.
以上就是c/c++编译的大概过程, 这里几个要注意的问题就是, 首先每个编译单独是独立编译的, 相互不影响的, 所以比如我们写一个头文件head.h
int global = 1;
如果有2个c/cpp文件包含了head.h, 这个时候编译不会出错, 但链接的时候就会出现重复定义错误, 因为global被定义了2次.因为同一个符号是全局唯一的, 只能定义一次, 由于编译是相互独立的, 即使这样写head.h
#ifndef __HEAD_H__
#define __HEAD_H__
int global = 1;
#endif
如果src1.cpp, src2.cpp都包含head.h,依然会出现同样的问题, 原因就在于编译相互独立, 条件编译#ifdef也是独立的, 控制语言#ifndef...只是确保head.h在同一个编译单元不被包含多次(如果出现, 就会出现重复定义的编译错误)这里特别注意的是, c++里面的类的处理并不一样, 我们一般把类定义写在一个头文件里面, 比如 test.h
#ifndef __TEST_H__
#define __TEST_H__
class Test {
public:
int x() { return _x; }
private:
int _x, _y;
};
#endif
但test.h却可以被多个cpp包含, 原因在于类的定义并不会生成目标代码, 也不会导出变量表, 所以类的定义头文件里面的函数都是inline函数(比如上面的x()), 类头文件不能定义非inline函数. 控制语句#ifndef是防止同一个编译单元里面有多处类定义.所以在写代码的时候要保证不要在头文件里面出现可能会生成目标代码的代码