01. 深入学习C++语言先要练好的内功

进程的虚拟地址空间

每个进程运行会有自己的虚拟地址空间,以x86的32位程序为例:

进程的虚拟地址空间.drawio

.rodata​数据段存储常量,包括字符串字面量,const常量等

.data​数据段保存初始化且不为0的变量,.bss​保存为初始化或初始化为0的变量,包括(静态和非静态)全局变量和静态局部变量

需要注意的是,这里的数据段(.rodata​、.data​和.bss​)不包含函数中的非静态局部变量,他是存储在栈上的,并通过指令初始化

不同进程都有自己的虚拟地址空间,其中用户空间是私有的,内核空间是公共的(因此,如果想要跨进程通信,则需要通过内核空间,例如匿名管道通信)

虚拟地址空间虚拟内存是计算机操作系统中的两个相关但不同的概念,以下是它们的区别和联系:


  1. 虚拟地址空间 (Virtual Address Space)
  • 定义:虚拟地址空间是进程可以访问的地址范围,是操作系统为每个进程分配的地址视图。

  • 作用

    • 为每个进程提供一个独立的、连续的地址空间,隔离进程之间的内存访问,确保安全性。
    • 使得进程的地址访问与物理内存无关。
  • 特点

    • 每个进程的虚拟地址空间是独立的,互不干扰。
    • 其大小由系统的架构和操作系统决定。例如,32位系统通常提供4GB的虚拟地址空间。
  • 例子:一个程序访问内存地址0x00400000​,该地址属于其虚拟地址空间,与实际的物理地址无关。


  1. 虚拟内存 (Virtual Memory)
  • 定义:虚拟内存是一种内存管理技术,它允许程序使用比实际物理内存更大的地址空间。

  • 作用

    • 提供了内存扩展功能,使程序可以运行在物理内存不足的情况下。
    • 通过页表机制将虚拟地址映射到物理地址。
    • 支持内存分页(Page)和换页(Swapping),提高内存利用率。
  • 特点

    • 物理内存不足时,操作系统会将部分内存数据交换到磁盘(如交换文件或页面文件)。
    • 使得多个程序可以共享有限的物理内存,同时仍然有各自的虚拟地址空间。
  • 例子:一个程序需要1GB的内存,但系统只有512MB物理内存,通过虚拟内存机制,程序仍然可以运行,部分数据被交换到磁盘。


区别

属性 虚拟地址空间 虚拟内存
定义 为每个进程提供的独立地址范围 用于管理内存和磁盘之间的交换机制
作用 提供独立的逻辑地址访问视图 扩展物理内存,提高内存利用率
是否依赖物理内存 不依赖物理内存 依赖于物理内存和磁盘之间的映射
关键技术 虚拟地址映射到物理地址 页表管理、分页与换页机制
范围 与CPU架构相关(如32位或64位系统) 实际大小取决于磁盘和物理内存的总容量

联系

  • 虚拟地址空间是虚拟内存管理的一部分,操作系统通过虚拟内存技术将虚拟地址空间中的地址映射到物理内存或磁盘。
  • 虚拟地址空间为进程提供逻辑上的地址独立性,而虚拟内存则确保在物理内存不足时,程序仍然可以运行。

总结来说,虚拟地址空间是逻辑上的概念,而虚拟内存是实现虚拟地址空间和物理内存交互的底层技术。

函数调用时的栈变化

以下面的简单程序为例:

int sum(int a,int b){
    int tmp = 0;
    tmp = a + b;
    return tmp;
}

int main(){
    int a = 1;
    int b = 2;
    int c = sum(a,b);
    return 0;
}

编译,并查看汇编代码:

g++ main.cpp -g
objdump -S a.out

得到的汇编代码如下:

0000000000001129 <_Z3sumii>:
int sum(int a,int b){
    1129:       f3 0f 1e fa             endbr64
    112d:       55                      push   %rbp
    112e:       48 89 e5                mov    %rsp,%rbp
    1131:       89 7d ec                mov    %edi,-0x14(%rbp)
    1134:       89 75 e8                mov    %esi,-0x18(%rbp)
    int tmp = 0;
    1137:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
    tmp = a + b;
    113e:       8b 55 ec                mov    -0x14(%rbp),%edx
    1141:       8b 45 e8                mov    -0x18(%rbp),%eax
    1144:       01 d0                   add    %edx,%eax
    1146:       89 45 fc                mov    %eax,-0x4(%rbp)
    return tmp;
    1149:       8b 45 fc                mov    -0x4(%rbp),%eax
}
    114c:       5d                      pop    %rbp
    114d:       c3                      ret

000000000000114e <main>:

int main(){
    114e:       f3 0f 1e fa             endbr64
    1152:       55                      push   %rbp
    1153:       48 89 e5                mov    %rsp,%rbp
    1156:       48 83 ec 10             sub    $0x10,%rsp
    int a = 1;
    115a:       c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)
    int b = 2;
    1161:       c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)
    int c = sum(a,b);
    1168:       8b 55 f8                mov    -0x8(%rbp),%edx
    116b:       8b 45 f4                mov    -0xc(%rbp),%eax
    116e:       89 d6                   mov    %edx,%esi
    1170:       89 c7                   mov    %eax,%edi
    1172:       e8 b2 ff ff ff          call   1129 <_Z3sumii>
    1177:       89 45 fc                mov    %eax,-0x4(%rbp)
    return 0;
    117a:       b8 00 00 00 00          mov    $0x0,%eax
    117f:       c9                      leave
    1180:       c3                      ret

栈帧示例图:

栈帧示意图.drawio

C++代码的编译和链接原理

预处理 编译 汇编 链接
解释 处理#​开头的预处理指令和宏(包括:宏定义、头文件展开、条件编译、删除注释) 生成汇编代码 生成可重定向的目标文件(二进制机器码)
此时,指令中符号的地址都是0
将多个可重定向目标文件和静态库链接,包括:
符号解析:符号定义的位置
符号重定位:给符号分配虚拟地址,并替换之前代码中对符号的引用地址
静态链接:将静态库代码复制到可执行文件中
指令 g++ -E xxx.cpp -o xxx.i g++ -S xxx.cpp -o xxx.s g++ -c xxx.s -o xxx.o g++ xxx1.o xxx2.o ... -o xxx

对于这份代码

// main.cpp
extern int sum(int a, int b);
int i = 0;
extern int j;
int main(){
    int a = i;
    int b = j;
    int c = sum(a,b);
    return 0;
}
// sum.cpp
int j = 0;
int sum(int a,int b){
    int tmp = 0;
    tmp = a + b;
    return tmp;
}

通过下面命令,我们可以观察符号表:

bingo@bingo:~/Code/learn$ g++ -c main.cpp -o main.o
bingo@bingo:~/Code/learn$ objdump -t main.o 

main.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 main.cpp
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 g     O .bss   0000000000000004 i # global 全局符号
0000000000000000 g     F .text  0000000000000037 main
0000000000000000         *UND*  0000000000000000 j # *UND* 表示在当前文件中未找到引用
0000000000000000         *UND*  0000000000000000 _Z3sumii


bingo@bingo:~/Code/learn$ g++ -c sum.cpp -o sum.o
bingo@bingo:~/Code/learn$ objdump -t sum.o 

sum.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 sum.cpp 
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 g     O .bss   0000000000000004 j
0000000000000000 g     F .text  0000000000000025 _Z3sumii


bingo@bingo:~/Code/learn$ g++ main.o sum.o -o my_program 
bingo@bingo:~/Code/learn$ objdump -t my_program 

my_program:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000              Scrt1.o
000000000000038c l     O .note.ABI-tag  0000000000000020              __abi_tag
0000000000000000 l    df *ABS*  0000000000000000              crtstuff.c
0000000000001070 l     F .text  0000000000000000              deregister_tm_clones
00000000000010a0 l     F .text  0000000000000000              register_tm_clones
00000000000010e0 l     F .text  0000000000000000              __do_global_dtors_aux
0000000000004010 l     O .bss   0000000000000001              completed.0
0000000000003df8 l     O .fini_array    0000000000000000              __do_global_dtors_aux_fini_array_entry
0000000000001120 l     F .text  0000000000000000              frame_dummy
0000000000003df0 l     O .init_array    0000000000000000              __frame_dummy_init_array_entry
0000000000000000 l    df *ABS*  0000000000000000              main.cpp
0000000000000000 l    df *ABS*  0000000000000000              sum.cpp
0000000000000000 l    df *ABS*  0000000000000000              crtstuff.c
00000000000020e8 l     O .eh_frame      0000000000000000              __FRAME_END__
0000000000000000 l    df *ABS*  0000000000000000          
0000000000003e00 l     O .dynamic       0000000000000000              _DYNAMIC
0000000000002004 l       .eh_frame_hdr  0000000000000000              __GNU_EH_FRAME_HDR
0000000000003fc0 l     O .got   0000000000000000              _GLOBAL_OFFSET_TABLE_
0000000000000000       F *UND*  0000000000000000              __libc_start_main@GLIBC_2.34
0000000000000000  w      *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000004000  w      .data  0000000000000000              data_start
0000000000001160 g     F .text  0000000000000025              _Z3sumii
0000000000004018 g     O .bss   0000000000000004              j
0000000000004010 g       .data  0000000000000000              _edata
0000000000001188 g     F .fini  0000000000000000              .hidden _fini
0000000000004000 g       .data  0000000000000000              __data_start
0000000000000000  w      *UND*  0000000000000000              __gmon_start__
0000000000004008 g     O .data  0000000000000000              .hidden __dso_handle
0000000000002000 g     O .rodata        0000000000000004              _IO_stdin_used
0000000000004020 g       .bss   0000000000000000              _end
0000000000001040 g     F .text  0000000000000026              _start
0000000000004014 g     O .bss   0000000000000004              i
0000000000004010 g       .bss   0000000000000000              __bss_start
0000000000001129 g     F .text  0000000000000037              main
0000000000004010 g     O .data  0000000000000000              .hidden __TMC_END__
0000000000000000  w      *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000  w    F *UND*  0000000000000000              __cxa_finalize@GLIBC_2.2.5
0000000000001000 g     F .init  0000000000000000              .hidden _init

在链接之前,符号表的地址全部为0,链接后为符号分配虚拟地址,并替换指令中对符号的引用

链接过程详解:

  • 符号解析

    • 所有对符号的引用,都要找到定义

      • 可能的问题:

        • 符号未定义
        • 符号重定义
  • 重定向

    • 给符号分配虚拟地址(在进程的虚拟地址空间中)
    • 替换指令中对符号的引用地址

可重定向目标文件的结构

一个可重定向的二进制文件的结构,可能如下图所示:

image

bingo@bingo:~/Code/learn$ readelf -S main.o 
There are 13 section headers, starting at offset 0x290:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000037  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  000001c0
       0000000000000048  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000077
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000078
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] .comment          PROGBITS         0000000000000000  00000078
       0000000000000027  0000000000000001  MS       0     0     1
  [ 6] .note.GNU-stack   PROGBITS         0000000000000000  0000009f
       0000000000000000  0000000000000000           0     0     1
  [ 7] .note.gnu.pr[...] NOTE             0000000000000000  000000a0
       0000000000000020  0000000000000000   A       0     0     8
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000c0
       0000000000000038  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000208
       0000000000000018  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  000000f8
       00000000000000a8  0000000000000018          11     3     8
  [11] .strtab           STRTAB           0000000000000000  000001a0
       000000000000001a  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  00000220
       000000000000006c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)

可重定向目标文件和可执行文件的对比

bingo@bingo:~/Code/learn$ readelf -h main.o 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          656 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 12
bingo@bingo:~/Code/learn$ readelf -h my_program 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1040
  Start of program headers:          64 (bytes into file)
  Start of section headers:          14032 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28
  • 可重定向目标文件入口地址为0,而可执行文件的入口地址是有效值

本文介绍了进程的虚拟地址空间和函数调用时的栈变化。首先,讨论了x86架构下32位程序的虚拟地址空间结构,包括代码段、数据段(`.rodata`​、`.data`​、`.bss`​)、堆和栈的布局。接着,通过一个简单的C++程序示例,展示了函数调用过程中栈的变化。文章详细描述了汇编代码中栈帧的创建、参数传递、局部变量的存储以及返回值的处理过程。最后,强调了虚拟地址空间和虚拟内存的区别与联系,指出虚拟地址空间是逻辑上的概念,而虚拟内存是实现虚拟地址空间和物理内存交互的底层技术。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇