首页 > 代码库 > 内部链接和外部链接以及const对象在头文件中的若干问题

内部链接和外部链接以及const对象在头文件中的若干问题

首先我们看一段示例代码:

t1.h

[cpp] view plaincopyprint?
  1. #ifndef T1_H_ 
  2. #define T1_H_ 
  3. #include "t2.h" 
  4.  
  5. #ifndef HHH 
  6. #define HHH 
  7.     int da = 5; 
  8. #endif 
  9.  
  10. #endif 
t2.h

[cpp] view plaincopyprint?
  1. #ifndef T2_H_ 
  2. #define T2_H_ 
  3.  
  4. const int  ca = 7; 
  5. extern int c; 
  6.  
  7. #endif 

[cpp] view plaincopyprint?
  1. #include "t2.h" 
  2. int c = 12; 

[cpp] view plaincopyprint?
  1. #include <iostream> 
  2. #include "t1.h" 
  3. #include "t2.h" 
  4.  
  5. using namespace std; 
  6.  
  7. int main() 
  8.     cout<<"da="<<da<<endl; 
  9.     cout<<"ca="<<ca<<endl; 
  10.     cout<<"c ="<<c<<endl; 
  11.     return 0; 

示例代码的程序的Makefile文件如下:

[cpp] view plaincopyprint?
  1. all: disklog 
  2. #which compiler 
  3. cc = g++ 
  4.  
  5. #where are include files kept 
  6. INCLUDE =  
  7.  
  8. #Option for development 
  9. CFLAGS = -g -Wall -ansi 
  10.  
  11. #Option for release 
  12. #CFLAGS = -o -Wall -ansi 
  13.  
  14. main: main.o t2.o 
  15.     $(cc) -L/usr/lib/mysql -lmysqlclient -o main main.o t2.o 
  16.  
  17. main.o: main.cpp t1.h t2.h 
  18.     $(cc) -I$(INCLUDE) $(CFLAGS) -c main.cpp t1.h t2.h 
  19.  
  20. t2.o: t2.cpp 
  21.     $(cc) -I$(INCLUDE) $(CFLAGS) -c t2.cpp t2.h 
  22.  
  23. clean: 
  24.     rm -f t2.o main.o  
当然如果你习惯window上用图形化界面的工具,可能熟悉gcc或者g++,以及编写makefile,但建议学习一下,明确各种编译的依赖关系,从而会让你更好的理解和解决编译链接中出现的错误。(但我不会在此重复,相信相关教程网上随手可得,入门可以参考博客,深入学习的推荐文档跟我一起写Makefile-陈皓)

首先我们来了解接个基本的概念问题。

编译单元:其实一个.c或者一个.cpp就是一个编译单元,很明显我们在上面的Makefile文件中可以看到t2.o和main.o两个目标文件,其实一个.cpp就是一个编译单元,他们的首先分别编译,然后再链接在一起。t2.o目标文件的生成是

[cpp] view plaincopyprint?
  1. t2.o: t2.cpp 
  2.     $(cc) -I$(INCLUDE) $(CFLAGS) -c t2.cpp t2.h 

main.o的目标文件生成是下面这段

[cpp] view plaincopyprint?
  1. main.o: main.cpp t1.h t2.h 
  2.     $(cc) -I$(INCLUDE) $(CFLAGS) -c main.cpp t1.h t2.h 
最后才通过

[cpp] view plaincopyprint?
  1. main: main.o t2.o 
  2.     $(cc) -L/usr/lib/mysql -lmysqlclient -o main main.o t2.o 
生成可执行文件main

我们大家都知道,在c++和c语言声明是可以多次出现的,定义只能出现一次。所以我们将全局变量c定义在t2.cpp中,在需要全局变量c的地方我们只需要加上一句extern int c;即可。那为什么需要这样呢?(当然我们上面的代码是完全没有问题的)。

假如把int c = 12。放入到t2.h中,我们会发现报错了,为什么呢?

我们来看下编译的过程,首先由编译的依赖关系,我们知道肯定首先编译的编译单元是t2.o,很明显t2.o中包含int c = 12这个定义,到目前没有任何问题,然后我们编译编译main.o,他依赖于main.cpp t1.h,t2.h,完成main.o的目标文件生成后,在main.o中肯定也包含int c = 12;当然到目前为止也没有任何问题,因为t2.o和main.o是两个不同的目标文件,他们分别生成,所以不会产生问题,接下来才是关键,生成可执行文件main,生成可执行文件main依赖t2.o和main.o。这是链接就会出现问题,我们在前面两步就知道t2.o和main.o中都有int c = 12;这样链接就出现问题,有两个int c = 12;肯定不行,重定义了。


不要以为把定义成这样,就安全了。

#ifndef HHH
#define HHH
int da = 5;
#endif

可能你以为只要在全局变量定义中加入预编译指令就可以避免重复定义,那你就错了。虽然上面的程序没有问题,但是如果在加一个t1.cpp,你就会发现出错了,代码如下:

[cpp] view plaincopyprint?
  1. #include "t1.h" 

因为在t1.o生成和main.o生成是独立,在t1.o生成时,编译器检测到HHH未定义,所以再定义HHH。但这个过程对main.o来说是透明的,所以在main.o生成过程HHH还是未定义的,所以又重复定义了int da = 5;但之前为什么没有报错呢?因为没有t1.cpp这个编译单元。


const 对象定义在头文件中为什么不会出错?


这里有些东西需要说明,const属于内部链接的,那么什么是内部链接,C++ primer说const对象是文件局部变量,但准确的解释应该是编译单元可见变量。

那什么是内部链接和外部链接呢?有个网友解释得很简洁明了,我就不重复了

一个.c .cpp为一个编译单元

内部链接就是该符号只在编译单元内有效,其他编译单元看不到。所以 多个编译单元中可有相同符号。const变量可以出现在多个.cpp文件中 而不会冲突就是因为是内部链接。

外部链接就是其他编译单元能看到当前编译单元的符号。如果有相同的外部链接符号,就会在链接时报重定义符号的错误。

对于main可执行文件的生成,虽然在t2.o中都有const int ca。但是它们都在编译器是局部可见,并不会产生冲突。实际上编译器的处理是用常量来直接替换const变量的使用(当然这里面包含了类型检查等操作),这样自然也就不存在冲定义的错误,当然这是编译器的特殊处理,是为了保证const int ca在替换#define 定义常量,但不会产生冲定义的冲突。

内部链接和外部链接以及const对象在头文件中的若干问题