4.4.4 内部连接和外部连接(Internal Linkage and External Linkage)
在这一小节中,我们来讨论一下C语言的内部连接Interal Linkage和外部连接External Linkage。C标准中关于连接Linkage的原文为:
An identifier declared in different scopes or in the same scope more than once can be made
to refer to the same object or function by a process called linkage . There are three kinds of linkage: external, internal, and none.
其大意为:出现在不同作用域或者相同作用域的多个重名声明,它们可代表内存中的同一个对象,而把多个重名声明对应到同一内存对象的过程称为连接Linkage,可分有外部连接ExternalLinkage、内部连接Internal Linkage和无连接None of Linkage。此处我们讨论的连接Linkage主要是针对重名的多个外部声明而言,而不是“连接器Linker所做的把多个编译后生成目标文件连接Linking成一个可执行程序”的工作。在英文的语境中,这两者分别对应的单词为Linkage和Linking,但在中文的语境中,都被译为“连接”。可能我们博大精深的汉语,在表达诗词歌赋方面也许天下无敌,但在描述科技术语时有时会略感乏力。其中的部分原因,可能源于计算机相关学科的很多术语本身就没有正式定义,比如热了好几年的“云计算”,可能要回答“什么是Cloud Computing”还真不是件容易的事情。而在亚洲热播的《甄嬛传》,传到美国时,老美的翻译在碰到名句“贱人就是矫情”时,也只好放弃。言归正传,《K&R》的附录”A11.2 Linkage”中有这样一段关于Linkage的原文:
The first external declaration for an identifier gives the identifier internal linkage if the static specifier is used, external linkage otherwise.
其大意为:在C文件中第一次出现的外部声明中,若有关键字static,则相应标识符为内部连接internal linkage,否则(“有extern关键字”或者“即无static也无extern”)为外部连接。
C标准还根据是否有初值,把外部声明分为定义Definition(带初值)和TentativeDefintion(译为尝试性定义或临时定义,不带初值且没有extern关键字,即不是形如” extern int a”的声明)。如果在C文件中出现了定义Definition,则与其同名的尝试性定义Tentative Defintion就只被当作冗余的声明;如果没有定义Defintion,则同名的多个尝试性定义会被合并成一个初值为0的定义。与其对应的原文为:
An external declaration for an objectis a definition if it has an initializer. An external object declaration that does not have an initializer, and does not contain the extern specifer, is atentative definition. If a definition for an object appears in a translation unit, any tentative definitions are treated merely as redundant declaration. Ifno definition for the object appears , all its tentative definitions become asingle Definition with initializer 0.
C标准引入内部连接、外部连接、定义和尝试性定义等概念的目的,主要是为了允许出现重名的多个外部声明External Declaration。外部连接ExternalLinkage对应的是“在多个C文件中出现的同名且类型相容的外部声明”,这些声明指向全局数据区中的同一个对象;而内部连接Interal Linkage对应的是“在同一个C文件中出现的同名且类型相容的外部声明”,这些声明指向的是静态数据区中的同一个对象。我们介绍过,一个进程的地址空间可分为:代码区、栈区、堆区和全局静态数据区。如果分得更细一点,我们还可把全局静态数据区分为全局数据区(存放全局变量),静态数据区(存放static变量)和常量区(存放浮点数和字符串等常量)。需要注意的是,此处讲的外部声明External Declaration是从C文法的角度出发,指的是在函数体外出现的声明,并不是“带关键字extern的声明”。由于在函数体内出现的局部变量和形参,其存储空间是在栈中动态分配,并不在全局静态数据区中,所以C标准规定这些标识符为“无连接None of linkage”,这意味着在函数体内的同一作用域中,不允许存在重名的声明。对于内部连接Internal Linkage来说,由于仅涉及到同一个C文件内的同名外部声明,C编译器本身就能处理;而对于外部连接ExternalLinkage而言,由于涉及到了多个C文件中的同名外部声明,最终需要由连接器Linker来处理。
这些规则有点晦涩,下面,我们举一个例子来说明,如图4.4.22所示。第2行的ok1为内部连接且是尝试性定义,而第4行的extern int ok1由于不是对ok1的第一次声明,所以其中的extern只表示第4行是个声明,既不是定义也不是临时性定义,所以第2和第4行的外部声明没有矛盾。第6和第8行的ok2由于不带extern和static关键字,所以被当作外部连接且为尝试性定义,第10行的ok2带有初值,为定义Definition,此时第6和第8行的ok2被当作冗余的声明。第12和第14行的ok3都为内部连接且为尝试性定义,不带有初值,最终会对应同一个static int对象。
图4.4.22 Linkage
在图4.4.22第16行的extern int err1只是对err1的声明,但由于是对err1的第一次声明,所以关键字extern还把err1置为外部连接,而第18行的声明则试图把err1设置为内部连接,这就产生了矛盾,C标准中称这种冲突的后果为“未定义的Undefined”,GCC和Clang编译器视这种矛盾为错误。C标准中关于此冲突的原文为:
If, within a translation unit, the same identifier appears with both internal and external linkage, the behavior is undefined.
而对于图4.2.22第20行中既没有extern也没有static的外部声明来说,err2为外部连接且为尝试性定义,这与第22行发生矛盾。第24和第26行的err3也有类似矛盾。第28和第30行都是对err4的定义,而定义只能有一个。第32和第34行则企图把err5声明为不相容的类型,我们在“C类型系统_相容类型”一节中介绍过,虽然int型和double型的变量可相互赋值,但int型和double型并不是相容类型。相容类型意味着内存中的数据不需要进行格式转换,就可以看成与之相容的另一种类型。而int型和double型之间的数据格式是不一样的,需要经过转换。第37行的ok3出现在函数体内,且带了extern关键字,由于我们在第12行已经把ok3设置为内部连接,所以第37行的ok3仍然是内部连接,且只是一个声明。而由于之前没有对ok4的外部声明,第39行的声明会把ok4设为外部连接。第41行的ok5则只是不带初值的局部变量,被视为“无连接”,即不允许在同一作用域中出现同名的ok5。
有了这几节的基础,我们再去看declchk.c中的函数CheckGlobalDeclaration、CheckFunction和CheckLocalDeclaration就会轻松了许多。C编译器剖析_4.4 语义检查_外部声明_内部连接和外部连接
原文:http://blog.csdn.net/sheisc/article/details/44651809