最近在阅读《大规模C++ 程序设计》 在第1部分,作者讨论了内链接和外链接问题(因为大规模的C++程序有繁多的类和单元。因此编译速度是个大问题)

这里记录一下关于内链接和外链接的理解。

 

  • C++的编译过程

     

    我们以bcb 和 vs 为例,一个程序文档一般都由 .cpp 文件 和 .h文件构成。但编译时,只有.cpp 参与编译。.h文件则会被预编译器复制到引用他的.cpp中。

    然后,.cpp文件被编译成.obj文件。

    接着,通过链接器,将obj文件链接为exe文件。

     

  • 链接过程
  1. 链接过程中,很重要的步骤,就是判断是否有重复定义。如果有重复定义,则判定为链接失败无法编译完成。

     

  2. 另一个重要步骤是,引用外部的定义。比如,在 a.cpp中定义了一个全局变量。b.cpp如果引用了这个全局变量。

    当经过编译分别得到a.obj 和 b.obj, 链接器发现b.obj引用了一个全局变量。则要去其他的.obj文件中搜索这个变量的定义。

    如果没有搜索到,则会报未定义的外部符号。然后无法编译完成。

 

  • 外部链接

     

    所谓外部链接,就是被外部引用的符号。这么说太抽象。下面是一个具体的例子:

     

    用vs建立一个VC++的控制台工程。

  1. 在stdafx.h中添加

#include
<iostream>

using
namespace std;

 

 

  1. 添加 a.cpp 和 b.cpp 源文件。
  2. a.cpp代码

#include
"stdafx.h"

 

int nA = 100;

  1. b.cpp代码

#include
"stdafx.h"

 

extern
int nA;

 

void printnA()

{

    cout<<nA<<endl;

}

  1. 编译代码。

    完全正常,那么链接器在链接b.obj的时候是如何知道这个nA 在哪里定义的呢?

    其本质,是因为我们指定了extern int nA 告诉链接器,这个变量是从外部引用的。

    链接器说:好,我去找找。于是就找到了a.obj。这就是外部链接过程。

  2. 修改b.cpp

#include
"stdafx.h"

 

//extern int nA;

int nA = 200;

 

void printnA()

{

    cout<<nA<<endl;

}

 

编译时,链接器则会输出错误

错误    1    error LNK2005: "int nA" (?nA@@3HA) 已经在 a.obj 中定义    D: \externtest\externtest\b.obj    externtest

错误    2    error LNK1169: 找到一个或多个多重定义的符号    D:\externtest\Debug\externtest.exe    1    1    externtest

 

这又是为什么呢? b.cpp 和 a.cpp 中nA 定义冲突了。

 

其本质原因是,在cpp中定义的外部变量,都是要参与外链接的。前面说了链接器会检查有没有重定义。

他现在a.obj中发现了 int nA, 接着又在 b.obj中发现了int Na,重复的外部符号。因此就报错了。

 

  • 内部链接
  1. 现在,修改b.cpp

#include
"stdafx.h"

 

//extern int nA;

//int nA = 100;

static
int nA = 100;

 

void printnA()

{

    cout<<nA<<endl;

}

 

我们将int nA 声明为静态的。编译链接就完全没有问题。为什么呢?

因为,静态的定义,都是内部链接。因此,链接并不会去检查重复。

(当然,如果在一个.cpp中定义了2个同名的静态变量,编译阶段就通不过,此时是编译器校验而不是链接器校验)

 

  1. 再修改b.cpp

#include
"stdafx.h"

 

//extern int nA;

//int nA = 100;

extern
int nA;

static
int nA = 100;

 

void printnA()

{

    cout<<"nA in b.cpp"<<nA<<endl;

}

 

再修改a.cpp

#include
"stdafx.h"

 

int nA = 300;

 

void PrintnA2()

{

    cout<<"nA in a.cpp"<<nA<<endl;

}

 

修改main函数

void printnA();

void PrintnA2();

 

int _tmain(int argc, _TCHAR* argv[])

{

    printnA();

    PrintnA2();

    return 0;

}

 

得到这样的结果

 

 

这时的情况是:b.cpp中外部引用了一个nA,还定义了一个静态内部Na.

但并不冲突,事实情况是,在b.cpp中凡是引用nA的地方,都引用的内部nA,既static 声明的nA

在a.cpp中,则依然是引用的全局的Na.

 

我们可以看到,c++ 对于同时存在内部和外部同名符号时,只认内部符号。

  • 函数的内,外链接
  1. 其实我们通过上面第8个例子,已经看到了。函数默认是外部链接的。而且可以不加extern 关键字。但是加了extern关键字也不错。

    我们修改main.cpp 用extern声明完全没有问题。

     

 

#include
"stdafx.h"

 

void printnA();

void PrintnA2();

//void printnA3();

extern
void printnA3();

 

int _tmain(int argc, _TCHAR* argv[])

{

    printnA();

    PrintnA2();

    printnA3();

    return 0;

}

 

 

这说明,函数也是外部链接的。只要声明了函数,链接器就去其他的obj中查找外部引用。

  1. 我们修改b.cpp,加入一个静态的printnA4

#include
"stdafx.h"

 

//extern int nA;

//int nA = 100;

extern
int nA;

static
int nA = 100;

 

void printnA()

{

    cout<<"nA in b.cpp"<<nA<<endl;

}

 

void printnA3()

{

    cout<<"::nA in b.cpp"<<::nA<<endl;

}

 

static

void printnA4()

{

    cout<<"static printnA4 in b.cpp"<<::nA<<endl;    

}

 

接着修改 main.cpp

 

#include
"stdafx.h"

 

void printnA();

void PrintnA2();

//void printnA3();

extern
void printnA3();

void printnA4();

 

int _tmain(int argc, _TCHAR* argv[])

{

    printnA();

    PrintnA2();

    printnA3();

    printnA4();

    return 0;

}

 

    编译,我们会得到一个报错

    

 

当我们把 b.cpp中的static注释,又会恢复正常。

 

和静态定义的变量一样。静态的函数也是内链接的。并不会被外链接访问(可以这么理解,当声明一个函数为static,就不想让外部(其他的obj)访问他)

 

  1. 继续修改b.Cpp,添加一个inline函数 printnA5 (为排除干扰,将10例子中的static注释掉)

    请注意 ::nA 这个写法,在b.cpp中并不会因为写了::nA 就访问去全局的nA了

#include
"stdafx.h"

 

//extern int nA;

//int nA = 100;

extern
int nA;

static
int nA = 100;

 

void printnA()

{

    cout<<"nA in b.cpp"<<nA<<endl;

}

 

void printnA3()

{

    cout<<"::nA in b.cpp"<<::nA<<endl;

}

 

//static

void printnA4()

{

    cout<<"static printnA4 in b.cpp"<<::nA<<endl;    

}

 

inline
void printnA5()

{

    cout<<"inline printnA5 in b.cpp"<<::nA<<endl;

}

 

 

同样,在main.cpp中去引用printnA5

 

#include
"stdafx.h"

 

void printnA();

void PrintnA2();

//void printnA3();

extern
void printnA3();

void printnA4();

void printnA5();

 

int _tmain(int argc, _TCHAR* argv[])

{

    printnA();

    PrintnA2();

    printnA3();

    printnA4();

    printnA5();

    return 0;

}

 

同样得到链接错误

 

这说明内联函数,也是无法参与外链接的。

  • 总结

     

    我们现在没有涉及类。仅仅涉及了C中的一些特性。但已经知道了什么内链接和外链接。

     

    我的理解是:外链接就是要让连接器去其他的obj中查找符号的引用行为。内链接则是不需要链接器去其他obj查找的引用行为。

     

     

    会发生外链接的行为(涉及到类之后,会有更多情况,这里只是一部分)

     

    • 引用了其他cpp中定义的全局变量。
    • 引用了其他cpp中定义的 非静态,非内联函数。

 

仅内链接的行为

  • 静态的变量
  • 静态函数
  • 内联函数

 

 

关于C++编译时内链接和外链接的更多相关文章

  1. 内连接,外链接(左连接、右连接、全连接),交叉连接大总结+附SQL JOINS图解[转]

    1.什么是连接查询呢? 概念:根据两个表或多个表的列之间的关系,从这些表中查询数据. 目的:实现多个表查询操作. 2.分类: 首先划分一下,连接分为三种:内连接.外连接.交叉连接 内连接(INNER ...

  2. 关于SqlServer的内连接,外链接以及left join,right join之间的一些问题与区别。

    就我个人理解通俗点来说内连接和外连接区别: 内连接 inner join或者 join (被默认为内连接) : 内连接的原理是:先进行语句判断和运行得出结果,然后在将结果连接起来,一般是横着连接. 外 ...

  3. linux下C/C++编译时系统搜索 include 和 链接库 文件路径的指定

     C/C++程序在linux下被编译和连接时,GCC/G++会查找系统默认的include和link的路径,以及自己在编译命令中指定的路径.自己指定的路径就不说了,这里说明一下系统自动搜索的路径.   ...

  4. My SQL的内连接,外链接查询

    1.内连接:只连接匹配的行. 2.左外连接:包含左边表的全部行,以及右边表中所有匹配的行,无论右边的表有没有和左边匹配的行,左边的所有行都必须要显示. 3.右外连接:包含右边表的全部行,以及左边表中所 ...

  5. sql 内连接和外链接

    如表     -------------------------------------------------     table1 | table2 |     ----------------- ...

  6. Mysql的内连接,外链接,交叉链接

    内连接:只连接匹配的行  inner join select A.*,B.* from A,B where A.id = B.parent_id 外链接包括左外链接,右外链接,全外链接 左外链接:包含 ...

  7. ROS知识(16)----如何编译时自动链接同一个工作空间的其他包的头文件(包含message,srv,action自动生成的头文件)

    catkin_make编译时,往往需要自动链接同一个工作空间的其他包的头文件.否则会出现类似如下的错误: /home/xx/xx_ws/srcA_package/src/db.hpp:13:26: f ...

  8. C++ 内连接与外连接 (转)

    啥叫内连接 外连接 我们知道编译的时候(假如编译器是VS),是以源文件cpp文件为单位,编译成一个个的obj文件,然后再通过链接器把不同的obj文件链接起来. 简单的说,如果一些变量或函数的定义是内连 ...

  9. MySQL (五)--连接查询简介、 交叉连接、 内连接、外连接、自然连接、温馨小提示

    1 连接查询简介 将多张表(可以大于2)进行记录的连接(按照某个指定的条件进行数据拼接). 最终结果:记录数可能会有变化,字段书一定会增加(至少两张表的合并). 连接查询:join,使用方式:左表 j ...

随机推荐

  1. 十步学习法 -- 来自<<软技能>>一书的学习方法论

    <<软技能>>第三篇“学习”,作者讲述了自己的学习方法:十步学习法.下面我用编程语言的方式来介绍. 十步学习法 伪代码介绍 # **这一步的目的不是要掌握整个主题,而是对相关内 ...

  2. linux netstat 查看网络连接状况

    netstat -lnpnetstat -an |grep 127.0.0.1 tcp 0.0.0.0:* LISTEN tcp 0.0.0.0:* LISTEN [root@wang /]# net ...

  3. 幻数浅析(Magic Number)

    在源代码编写中,有这么一种情况:编码者在写源代码的时候,使用了一个数字,比如0x2123,0.021f等,他当时是明白这个数字的意思的,但是别的程序员看他的代码,可能很难理解,甚至,过了一段时间,代码 ...

  4. 使用Dockerfile创建ssh服务的镜像02

    使用Dockerfile创建ssh服务的镜像02 1:创建工作目录---一个镜像的所有文件都放这个目录下 ubuntu@ubuntu:~$ mkdir sshd_ubuntu ubuntu@ubunt ...

  5. XML文件介绍

    xml基础详解 1.概述: xml:即可扩展标记语言,xml是互联网数据传输的重要工具,它可以跨越互联网的任何平台,不受编程语言和操作系统的限制,可以说他是一个拥有互联网最高级别通行证的数据携带者.x ...

  6. DecodingGenome(CodeForces-222E)【矩阵快速幂】

    题目链接:https://vjudge.net/contest/333591#problem/L 题意:用m个字符构成长度为n的串,其中存在形如“ab”(表示a后不能放置b)的条件约束,问共有多少种构 ...

  7. Codeforces 1189F. Array Beauty

    传送门 首先可以注意到序列里面元素的顺序对答案是没有影响的,所以二话不说先排序再看看怎么搞 考虑枚举每种子序列可能产生的贡献并算一下产生这个贡献的子序列有多少 考虑设 $F(x)$ 表示选择的元素差值 ...

  8. Win10环境下,告别MarkdownPad,用Notepad++搭建编写md文档的环境

    1. 为什么抛弃MarkdownPad 2 ? MarkdownPad坊间号称 Windows 环境下最好用的markdown编辑器-EXO me??? 博主入MarkdownPad 2 坑就是因为这 ...

  9. Java写学生管理系统

    package Homework08;/*调试了一上午,收获:学会了昨天的debug的使用吸取教训:Student stus[]=new Student[2]; for (int i=0;i<s ...

  10. IOC+EF+Core搭建项目框架(三)

    /// <summary> /// 表示类别映射配置 /// </summary> public partial class sys_UserMap : NopEntityTy ...