编译过程的一些小知识——内部连接与外部连接

转载 2012年03月31日 09:33:07


首先,我们来了解下定义:

内部连接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它。

外部连接:如果一个名称对编译单元来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。

 

通过对LIB与DLL的讲解,我们可以更方便的理解内部连接与外部连接。

我们了解了一个编译单元(.cpp)编译成obj文件后,至少还会有未解决符号表、导出符号表、地址重定向表。而如果这个名称是内部连接的话,那在导出符号表中不存储它的入口。也就是别的obj文件无法链接到这个名称。而外部连接刚好相反,在导出的符号表中有它入口。

 

以下情况有内部连接:
a)所有的声明
b)名字空间(包括全局名字空间)中的静态自由函数、静态友元函数、静态变量的定义
c)enum定义
d)inline函数定义(包括自由函数和非自由函数)
e)类的定义
f)名字空间中const常量定义
g)union的定义

以下情况是外部连接:

a)非static全局变量与全局函数

b)类非inline函数总有外部连接。包括类成员函数和类静态成员函数
c)类静态成员变量总有外部连接
d)名字空间(包括全局名字空间)中非静态自由函数、非静态友元函数及非静态变量

好了,我们通过程序来深刻的理解吧:

假设有3文件:

TestBase.h:                       TestRun.cpp                          TestError.cpp

int a;                                    #include "TestBase.h"            #include "TestBase.h"

当然还有一个包含main()方法的Test.cpp输出的文件。

#include "TestBase.h"  

extern int a;

void print()

{

    cout << a<<endl;

}

分别编译TestRun与TestError我相信大家都能通过编译,但是你链接的时候肯定会出错的,提示的信息有一句为:Debug/Test.exe : fatal error LNK1169: one or more multiply defined symbols found(一个或多个定义符号被发现)。因为非static的全局变量是外部连接的,其实也就是说TestRun.obj与TestError .obj的导出符号表中都对a导出了信息入口(别问我为什么导出了它,因为编译器默认对非static全局变量都导出了,想知道怎么实现的,可以去找Microsoft)。而当我的Test.cpp中要用到a时我到底是用TestRun.obj还是TestError .obj中导出符号表中的a呢?所以链接肯定会出错的。

我们在尝试着把TestBase.h中的全局变量a改了static。马上能编译与链接成功,并输出0,其实是因为static全部变量是内部连接的,obj文件的导出符号表中没有提供a符号的入口。而Test.cpp用到的是自己在编译的时候得到的a的信息,也就是TestBase.h文件中a的默认值0.

函数的性质也是一样的,所以大家只需要知道你声明或定义的名称是内部连接还是外部连接,如TestBase.h中的非全局变量a是外部连接。你就会明白语法其实也就那么回事了。

让我们在来了解class里的static变量和非static函数是什么样子的。

class是内部连接的,这就是为什么可以有多个cpp文件能包含它的原因了,但是如果我在class里写了个static变量了,那这个变量就是外部连接了。而内中的非static函数了是外部连接。所以针对class我总结了下3点必须:

(1)。类中的static变量请不要在声明类中定义。一般我们类的声明是写到.h文件中,而实现写到对应的cpp文件中的,理由看程序。

还是刚才3个文件:

TestBase.h:                                        TestRun.cpp                                       TestError.cpp

class A                                               #include "TestBase.h"                        #include "TestBase.h" 
{                                                             int A::getA()
public:                                                  {
     void setA( int a );                            return m_a;
                 int getA();                            }
                 inline int getB();                  void A::setA( int a )
private:                                                  {
                int m_a;                                              m_a = a;
                static int m_b;                        }
};

int A::m_b = 5;

编译都成功后,链接会出现如下信息:

TestError.obj : error LNK2005: "private: static int A::m_b" (?m_b@A@@0HA) already defined in Test.obj
TestRun.obj : error LNK2005: "private: static int A::m_b" (?m_b@A@@0HA) already defined in Test.obj
Debug/Test.exe : fatal error LNK1169: one or more multiply defined symbols found

也就是说类中的static变量m_b为外部连接,理由同上面写的全局变量a一样。解决办法是把int A::m_b = 5;写到类的实现文件TestRun.cpp 中,这样就只有TestRun.obj文件的导出符号表中提供了m_b,而其他的obj文件如果需要的话,那这只能在它的未解决符号表中存在了,它自然只能在TestRun.obj文件中找到m_b的入口,这样就无任何冲突了。所以类中的static变量请一定不要在声明类中定义。

(2)。内中的非static函数请一定也不要在声明类实现,除非你的声明和实现是写到一起的。

TestBase.h:                                           TestRun.cpp                                       TestError.cpp

class A                                                  #include "TestBase.h"                          #include "TestBase.h" 
{                                                             int A::getA()
public:                                                  {
     void setA( int a );                        return m_a;   
                 int getA();                            }
                 inline int getB();                  int  A::m_b = 6;
private:                                                  
                int m_a;         

                 static int m_b;                                                                
};    

void    A::setA( int a )

{

    m_a = a;

}          

编译通过,链接后肯定不能通过,原因都一样,就是因为类中的非static函数是外部链接。而如果你把定义和声明写到一起,那就没问题,但你应该知道这样写就是相当于出卖了自己,你把你的实现代码都给了别人。

(3)inline函数请一定要在类的声明文件中实现。大家应该看到类中的inline函数我没有写它的实现代码,其实也就是为了这条定义一样。你可以查看VC提供的头文件中inline的定义与实现都是写在头文件中的。

所以那个inline函数你必须写在TestBase.h内。        

inline int A::getB()
{
         return m_b; 
}            

为什么这样呢?因为inline函数是内部连接,它不在导出符号表。假如你把inline int getB()的实现代码写在了 TestRun.cpp中,那链接后的错误信息是:

Debug/Test.exe : fatal error LNK1120: 1 unresolved externals

也就是别的cpp文件就无法用到找到它了。

好了,其实很多东西如果是在链接时候出错了,或者为什么语法这么实现的,你可以从它是内部连接还是外部链接入手,你会发现很多规律以及规则的存在,这样你的编写的C++的代码安全性一定会大大的提高。相信我!


编译过程的一些小知识——LIB与DLL的区别

相信很多人都用过VC6.0与Visual Studio系列产品。 也有很多牛B人士用它们创造过很人的神话,铸就许多美丽的传说。 那你们知道为何你能用它创始出那么多的奇迹,这是你一个人的成功...

从编译过程看内部类和lambda表达式

什么是内部类内部类按名称分为:匿名内部类,和非匿名内部类。 非匿名内部类又分为:静态内部类和非静态内部类。有时候我们会发现,修改外部类的某个方法使得它访问了内部类的某个方法,编译之后就会发现字节码中...

Android源码内部编译过程总结(Make)(转)

Android的优势就在于其开源,手机和平板生产商可以根据自己的硬件进行个性定制自己的手机产品,如小米,LePhone,M9等,因此,在我们在对Android的源码进行定制的时候,很有必要了解下,An...
  • redouba
  • redouba
  • 2014年04月30日 15:24
  • 575

Android源码内部编译过程总结(Make)(转)

Android的优势就在于其开源,手机和平板生产商可以根据自己的硬件进行个性定制自己的手机产品,如小米,LePhone,M9等,因此,在我们在对Android的源码进行定制的时候,很有必要了解下,An...

计算机基础知识——“该命令不是内部或外部命令”的解决方法

在Windows XP系统的命令行模式中运行所有命令都提示“该命令不是内部或外部命令,也不能运行可执行文件和或批处理文件”。我认为造成这种情况的原因不为乎以下几种情况: 1、执行这些命令的可执行文件被...

Linux 驱动开发 基础知识及编译过程

buzzer_driver.ko的驱动编译过程说明 在下列目录下执行"make"命令编译buzzer_driver.c 得到下列文件,其中有buzzer_driver.ko, 并装载此驱动模块 ...

C代码编译过程及汇编相关知识

一、汇编程序大小写后缀的区别 .s     汇编语言源程序;汇编 .S     汇编语言源程序;预处理,汇编 小写的s文件,在后期阶段不在进行预处理操作,所以我们不能在这里面写预处理...
  • bdwkyy
  • bdwkyy
  • 2012年02月15日 14:46
  • 356

编译过程的一些知识,针对C/C++

——摘自《高级C/C++编译技术》 一、 前言 编译过程粗略的划分为几个阶段: 1.预处理阶段 2.语言分析阶段 3.汇编阶段 4.优化阶段 5.代码生成阶段 二、 详细介绍 1.预处理阶段...

编译过程知识点-------笔记

编译过程分为4段: 预处理 编译 汇编 链接 注:现在版本的GCC把预处理和编译两个步骤合成一个步骤,用cc1工具来完成。gcc其实是后台程序的一些包装,根据不同参数去调用其他的实际处理程序,比...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:编译过程的一些小知识——内部连接与外部连接
举报原因:
原因补充:

(最多只允许输入30个字)