C++编译原理

参考文章:http://blog.sina.com.cn/s/blog_65fbc556010162j6.html
C++模块编译时将变量及函数的地址存在三个表里:

1. 符号导出表:本编译单元定义并且愿意提供给其他编译单元的符号及其地址

2. 未解决符号表:本单元引用但定义并不在本单元的所有符号及其地址
3. 地址引用表:本编译单元所有对自身地址的引用记录

1.cpp
int n = 1; 

    void f() 
     { 
        ++n; 
    } 
2.cpp

 extern int n; 
    void g() 
    { 
        ++n; 
    } 
未解决符号表和导出符号表 ,分别告诉链接器 自己需要什么和能够提供什么
地址重定向表是为了  链接器会在拼接的时候对各个单元的地址进行调整,避免拼接的时候重复。

链接器在进行链接的时候:

首先决定各个目标文件在最终可执行文件里的位置。

然后访问所有目标文件的地址重定向表,对其中记录的地址进行重定向(即加上该编译 单元实际在可执行文件里的起始地址)。

然后遍历所有目标文件的未解决符号表,并且在所有的导出符号表里查找匹配的符号,并在未解决符号表中所记录的位置上 填写实际的地址(也要加上拥有该符号定义的编译单元实际在可执行文件里的起始地址)。

最后把所有的目标文件的内容写在各自的位置上,再作一些别的工作,一 个可执行文件就出炉了 

 unresolved external link.. 
    
这个很显然,是链接器发现一个未解决符号,但是在导出符号表里没有找到对应的項。 
    
解决方案么,当然就是在某个编译单元里提供这个符号的定义就行了。(注意,这个符号可以是一个变量,也可以是一个函数),也可以看看是不是有什么该链接的文件没有链接 
    duplicated external simbols... 
    
这个则是导出符号表里出现了重复项,因此链接器无法确定应该使用哪一个。这可能是使用了重复的名称,也可能有别的原因。 

   
我们再来看看C/C++语言里针对这一些而提供的特性: 
    extern:
这是告诉编译器,这个符号在别的编译单元里定义,也就是要把这个符号放到未解决符号表里去。(外部链接) 
     
    static:
如果该关键字位于全局函数或者变量的声明的前面,表明该编译单元不导出这个函数/变量的符号。因此无法在别的编译单元里使用。(内部链接)。如果是static局部变量,则该变量的存储方式和全局变量一样,但是仍然不导出符号。 
     
    
默认链接属性:对于函数和变量,模认外部链接,对于const变量,默认内部链接。(可以通过添加externstatic改变链接属性) 

 
   外部链接的利弊:外部链接的符号,可以在整个程序范围内使用(因为导出了符号)。但是同时要求其他的编译单元不能导出相同的符号(不然就是duplicated external simbols) 

   内部链接的利弊:内部链接的符号,不能在别的编译单元内使用。但是不同的编译单元可以拥有同样名称的内部链接符号。 

  
********* 为什么头文件里一般只可以有声明不能有定义*********

头文件可以被多个编译单元包含,如果头文件里有定义,那么每个包含这个头文件的编译单元就都会对同一个符号 进行定义,如果该符号为外部链接,则会导致duplicated external simbols。因此如果头文件里要定义,必须保证定义的符号只能具有内部链接。 

    
*********为什么常量默认为内部链接,而变量不是*********
        
这就是为了,能够在头文件里如const int n = 0这样的定义常量。由于常量是只读的,因此即使每个编译单元都拥有一份定义也没有关系。如果一个定义于头文件里的变量拥有内部链接,那么如果出现多个编译 单元都定义该变量,则其中一个编译单元对该变量进行修改,不会影响其他单元的同一变量,会产生意想不到的后果。 

  
********* 为什么函数默认是外部链接*********
        
虽然函数是只读的,但是和变量不同,函数在代码编写的时候非常容易变化,如果函数默认具有内部链接,则人们会倾向于把函数定义在头文件里,那么一旦函数 被修改,所有包含了该头文件的编译单元都要被重新编译。另外,函数里定义的静态局部变量也将被定义在头文件里。 

    
为什么类的静态变量不可以就地初始化:所谓就地初始化就是类似于这样的情况: 
        class A 
        { 
            static char msg[] = "aha"; 
        }; 
不允许这样做得原因是,由于class的声明通常是在头文件里,如果允许这样做,其实就相当于在头文件里定义了一个非const变量。 

    
C++里,头文件定义一个const对象会怎么样: 
        
一般不会怎么样,这个和C里的在头文件里定义const int一样,每一个包含了这个头文件的编译单元都会定义这个对象。但由于该对象是const的,所以没什么影响。但是:有2种情况可能破坏这个局面: 
        1
。如果涉及到对这个const对象取地址并且依赖于这个地址的唯一性,那么在不同的编译单元里,取到的地址可以不同。(但一般很少这么做) 
        2
。如果这个对象具有mutable的成员变量,某个编译单元对其进行修改,则同样不会影响到别的编译单元。 

   
为什么类的静态常量也不可以就地初始化: ???????
        
因为这相当于在头文件里定义了const对象。作为例外,int/char等可以进行就地初始化,是因为这些变量可以直接被优化为立即数,就和宏一样。 

    
内联函数: 
        C++
里的内联函数由于类似于一个宏,因此不存在链接属性问题。 

    
为什么公共使用的内联函数要定义于头文件里: 
        
因为编译时编译单元之间互相不知道,如果内联函数被定义于.cpp文件中,编译其他使用该函数的编译单元的时候没有办法找到函数的定义,因此无法对函数进行展开。所以说如果内联函数定义于.cpp文件里,那么就只有这个cpp文件可以是用这个函数。 

    
头文件里内联函数被拒绝会怎样: 
        
如果定义于头文件里的内联函数被拒绝,那么编译器会自动在每个包含了该头文件的编译单元里定义这个函数并且不导出符号。 

    
如果被拒绝的内联函数里定义了静态局部变量,这个变量会被定义于何处: 
        
早期的编译器会在每个编译单元里定义一个,并因此产生错误的结果,较新的编译器会解决这个问题,手段未知。 

    
为什么export关键字没人实现: 
        export
要求编译器跨编译单元查找函数定义,使得编译器实现非常困难。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值