C++复习 之编译链接原理与虚拟地址空间

编译链接原理

1)(.cpp文件)  预编译   ( 生成 .i文件)
      1. 将所有的 "#define" 删除 ,并展开所有的宏;
      2. 处理掉所有条件预编译指令;如:"#if" ,"#ifdef" ,"#elif" ,"#else", "#endif" ;
      3. 处理 “#include" 指令 (递归过程);
      4. 删除所有的注释 : " // "  ''/*  */'' ;
      5. 添加行号和文件名标识 ;
      6. 保留所有的 #pragma 编译器指令 ,待编译器使用 ;

2)编译(生成.s文件)
      1.词法分析
      2.语法分析
      3.语义分析
      4.代码优化
      5.生成汇编指令

3)汇编(生成.o文件,也叫目标文件)
       1.翻译指令
       将汇编代码转变成机器可以执行的指令,
       每一个汇编语句几乎都对应一条机器指令。

4)链接(生成.exe文件,也叫可执行文件)
       1.合并段和符号
       2.符号解析
       3.分配地址和空间
       4.符号重定位
       
1.弱符号:全局的未初始化的符号
  强符号:全局的已初始化的符号
        链接阶段链接器只关注符号表中的全局符号
        强弱符号的选取规则:
          1. 两个强符号  编译报错 
          2. 一个强符号 一个弱符号  选择强符号
          3. 两个弱符号 根据不同编译器处理方式不同
 2.符号表: 外部符号
           符号解析:在每个文件符号引用(引用外部符号)的地方找到符号的定义;
           符号:存放在.data和.bss段的变量都要有一个名称来标识这个变量,这个名称称为符号
           符号重定向:就是对.o文件中.text段指令中的无效地址给出具体的虚拟地址或者相对位移偏移量。
 3.指令段:虚假的地址和偏移

虚拟地址空间

1.虚拟地址空间

1.创建内核映射结构体,并建立虚拟地址空间和物理内存的映射;
2.加载指令和数据;
3.把入口地址写入下一行指令寄存器

2.虚拟地址空间布局:(4G,ALU宽度)

在这里插入图片描述

       栈区:局部变量、函数栈帧、形参变量;
       堆区:malloc 或 new 开辟的内存;
      数据段:全局变量、静态全局变量,静态局部变量;
            (.data:已初始化,或初始化值不为0;.bss:未初始化或初始化值为0)
     text段:指令
  .rodata段:常量字符串,如:"hello word";

栈区和堆区的区别:
栈区内存: 
    1.由系统自动分配,系统自动释放,以函数为单位进行栈内存分配;其操作方式类似于数据结构中的栈;
    2.内存分配释放 速度快效率高,内存连续;
    3. 栈是由高地址向低地址扩展的连续内存;
    4. 在 linux 系统上大家是可以通过 ulimit -s 来查看默认的栈大小的,
         linux中栈的默认大小是 10M,windows 系统默认的栈大小是 1M;
         
堆区内存:
    1.堆:需要程序员自己申请,并指明大小。
         在c中malloc函数如p1 = (char *)malloc(10);
         在C++中用new运算符,但是注意p1、p2本身是在栈中的,因为他们还是可以认为是局部变量。
         若程序员不释放,程序结束时可能由OS回收 , 分配方式类似于链表;
         堆内存需要用户自己管理,因此堆内存容易造成内存泄漏;
    2.堆内存的分配释放相对栈来说效率低一些,内存不一定连续,容易产生内存碎片,但灵活性高;
    3.堆是由低地址向高地址扩展的非连续内存,影响堆大小的因素比较多,和系统有效虚拟内存的大小有关;
    4.堆空间的大小和当前系统的物理内存大小,交换分区大小,栈的大小,所使用共享库的大小都息息相关,
        因为它们都属于用户空间部分,x86 32bit linux 系统默认给用户空间是 3G,

   当我们在代码上分配空间的时候,不管是栈还是堆,其实都只是在虚拟地址空间上分配的内存空间,
   当真正进行读写使用的时候,随着不断的发生缺页异常,才会去分配真正的物理内存和虚拟地址空间
   上的页面进行映射。缺页异常处理函数在内核上是 do_page_fault,
   越界访问栈空间,内核会在一定范围对栈的空间进行增长的。

内存分配方式有几种

三种   1.数据段   2.栈   3.堆
  
  数据段(静态存储区域)分配: 
       内存在编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
   在栈上创建:
       在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
       栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
   在堆上分配: 
        亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,
        程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,
        但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏;
        频繁地分配和释放不同大小的堆空间将会产生堆内碎块;

全局变量和局部变量是有什么区别,是怎么实现的

  全局变量是整个程序都可以访问的,生存周期从程序运行开始到运行结束;
  局部变量只有子模块可以访问,生存周期从定义点到子模块结束;
  全局变量在数据段,程序运行前被加载;
  局部变量在堆栈上,运行时分配内存;
 
   操作系统和编译器通过内存分配的位置来实现的,
   全局变量分配在全局数据段并且在程序开始运行的时候被加载;局部变量则分配在堆栈里面 ;
                                                                        
1.从作用域看:
      全局变量:其作用范围是“整个工程”,只需在一个源文件中定义,就可以作用于所有的源文件。
                  当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量;
  静态全局变量:使用 static 关键字修饰,也具有全局作用功能,和全局变量区别在于如果该程序包含多个文件,
                  其作用范围仅在定义的那个文件,不能作用于其它文件,
                  这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量;
      局部变量:仅仅从定义的位置开始,到定义它的右花括号结束,只在函数执行期间存在,
                   函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回;
   静态局部变量:局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,
                   和局部变量的区别在于函数执行完也还存在;
2.从生存期看:
        全局变量:      随进程持续性
     静态全局变量:      随进程持续性
         局部变量:     从定义开始到函数结束,函数调用后变量就被撤销,内存被回收
     静态局部变量:      随进程持续性,static 修饰的局部变量其生存期从函数变为整个进程
     
3.从内存分配看:
          全局变量:       全局(静态)存储区    //.data  .bss
      静态全局变量:       全局(静态)存储区    // .data   .bss
          局部变量:       存放在栈中,只有在所在函数被调用时才动态地为变量分配存储单元   //.stack
       静态局部变量:       全局(静态)存储区  //.data
       
 举举例说明全局变量的优缺点 ?
    优点:
      1)全局可见,任何 一个函数或线程都可以读写全局变量-同步操作简单。
      2)内存地址固定,读写效率比较高。
    缺点:
   1)全局变量存放在静态存储区,系统需要为其分配内存,一直到程序结束,才会释放内存,
          这一点就局部变量的动态分配,随用随从栈中申请,用完(函数调用完毕)就释放。
   2)影响函数的封装性能:我们肯定是希望我们写的函数具有重入性,就如一个黑盒子一般,
          只通过函数参数就能得到返回,内部实现要独立,但是如果函数中使用了全局变量,
          这势必就破坏了函数的封装性,会造成对全局变量的依赖。
   3)降低函数的移值性,原因同上。
   4)降低代码的可读性,这也意味着系统维护会不方便,因为一个全局变量可能会出现程序中的各个环节,
          函数的执行也会根据环境变化而变化,所以调试会不太方便。
    5)全局变量的读写,可能会延迟,这主要是体现在“写”操作上,由于写操作,一般需要2个周期操作,
          所以有可能会出现,这边没写完时,那边已经读了,结果读到的不是最终值,这个是一个概率事件,
          概率很小,但是并不代表没有。

寄存器

   1. ebp         栈底指针寄存器
   2. esp         栈顶指针寄存器
   3. ###PC       下一行指令寄存器
   4. ELF         Linux的ELF格式的文件解析器

返回值:
    <= 4      eax
    <=8  >4   eax  edx
    >8        临时量

32位通用目的寄存器的指定用途如下:
  eax : 累加器
  ecx:计数器
  edx:I/O指针
  ebx:DS段的数据指针
  esp:堆栈(stack)指针
  ebp:SS段的数据指针
  esi:字符创操作的源指针:SS段的数据指针
  edi:字符创操作的目标指针:ES段的数据指针
  汇编就是“寄存器与寄存器” 或者 “寄存器与内存” 之间来回移动数据;

函数的开栈

 1.压入实参 ,自右向左;
 2.压入下一行指令地址;
 3.压入调用函数的栈底指针寄存器的值;
 4.跳转到被调用方函数栈帧;
 5.被调用方开辟局部变量活动的空间并初始化;(CCCC CCCC 内核地址)

调用约定

     _cdecl       C标准调用约定
     _stdcall     Windows标准调用约定
     _fastcall    快速调用约定
     _thiscall    类成员方法调用约定,依赖对象调用;
   
   作用:
     1.函数符号的生成
     2.实参的入栈顺序 ==> 自右向左
     3.形参的开辟,清理方式
     
     _cdecl       调用方开辟,调用方清理
     _stdcall     调用方开辟,被调用方清理
     _fastcall    两个参数 ,寄存器
     _stdcall     两个参数 ,处理方式相同
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值