从汇编代码直观看i++和++i的区别

i++和++i是C语言入门最基础的一个知识,以前学的时候,仅仅是记住,i++是先赋值再加1,++i是先相加后赋值。前段时间在一个循环buffer的读写指针访问时,不小心使用了i++导致,程序一直报段错误,由此我试着从更底层的角度来理解下i++和++i。

🎬个人简介:一个全栈工程师的升级之路!
📋个人专栏:C/C++精进之路
🎀CSDN主页 发狂的小花
🌄人生秘诀:学习的本质就是极致重复!

目录

1 用几个例子详细看i++和++i的区别

2 i++和++i的理解


1 用几个例子详细看i++和++i的区别

第一步:

        这是有i++和++i的C代码

第二步:       

        使用gcc -S func_call.c -o func_call.s 得到汇编代码

i++ 汇编代码
++i 汇编代码

第三步:        

        逐行解释i++的汇编代码(如下):

1. `.file "func_call.c"`:指定当前文件名为"func_call.c"。
2. `.text`:表示接下来的代码是文本段,即程序的可执行部分。
3. `.globl main`:声明全局变量main,使得其他文件中可以调用这个函数。
4. `.type main, @function`:设置main函数的类型为函数。
5. `main:`:开始定义main函数。
6. `.LFB0:`:局部帧缓冲区(Local Frame Buffer)的起始地址。
7. `.cfi_startproc`:表示开始处理过程。
8. `pushq %rbp`:将基址指针寄存器rbp压入栈中。
9. `.cfi_def_cfa_offset 16`:设置当前帧的基址偏移量为16字节。
10. `.cfi_offset 6, -16`:设置栈顶元素相对于rbp的偏移量为-16字节。
11. `movq %rsp, %rbp`:将栈顶指针赋值给基址指针寄存器rbp。
12. `.cfi_def_cfa_register 6`:设置当前帧的基址寄存器为rbp。
13. `movl $0, -8(%rbp)`:将0赋值给rbp-8的位置。
14. `movl -8(%rbp), %eax`:将rbp-8位置的值赋给eax寄存器。
15. `leal 1(%rax), %edx`:将rax寄存器的值加1后的结果赋给edx寄存器。
16. `movl %edx, -8(%rbp)`:将edx寄存器的值赋给rbp-8的位置。
17. `movl %eax, -4(%rbp)`:将eax寄存器的值赋给rbp-4的位置。
18. `movl $0, %eax`:将0赋值给eax寄存器。
19. `popq %rbp`:将栈顶元素弹出并赋值给基址指针寄存器rbp。

20. `.cfi_def_cfa 7, 8`:设置当前帧的基址寄存器为rbp,基址偏移量为8字节。
21. `ret`:返回到调用者。
22. `.cfi_endproc`:表示过程处理结束。
23. `.LFE0:`:局部帧缓冲区(Local Frame Buffer)的结束地址。
24. `.size    main, .-main`:设置main函数的大小为从开始到结束的距离。
25. `.ident    "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"`:标识编译器信息。
26. `.section    .note.GNU-stack,"",@progbits`:定义一个名为.note.GNU-stack的节,用于存储栈的信息。

重点关注13~19行:

13行:对应c代码 int i = 0; 将0赋值给栈帧中的rbp-8的位置,由于该段代码有两个int类型局部变

           量,因此需要8个字节存储局部变量,rbp-8就是栈顶指针偏移8个字节,栈是由高到底的,

           因此是减去8,即i变量在栈帧的首地址为rbp-8

14行: 将rbp-8位置的值赋给eax寄存器,将i给累加器eax,eax中的值为0

15行:得到edx寄存器的值为1

16行:将edx中的值1赋值给栈帧中的rbp-8,完成i++,此时i = 1;

17行:将eax中的值0赋值为栈帧中的rbp-4位置,即a = 0;

18行:将0赋值给eax寄存器,eax一般用来存放函数的返回值

19行:栈顶元素出栈rbp-8存储i++后的i = 1,rbp-4存储a = 0。

由此可以得到int a = i++;中的a = 0;

        逐行解释++i的汇编代码(如下):

1. `.file "func_call.c"`:这是指定当前文件名的指令,表示这个文件的名字是"func_call.c"。
2. `.text`:这是一个段描述符,表示接下来的代码是程序的文本部分。
3. `.globl main`:这是一个全局变量声明,表示main函数在其他地方也可以被调用。
4. `.type main, @function`:这是一个类型声明,表示main函数的类型是函数。
5. `main:`:这是main函数的开始。
6. `.LFB0:`:这是一个局部帧缓冲区(Local Frame Buffer)的开始地址。
7. `.cfi_startproc`:这是一个控制流指示器(Control-Flow Indirection),表示开始处理过程。
8. `pushq %rbp`:将基址指针寄存器rbp的值压入栈中。
9. `.cfi_def_cfa_offset 16`:设置当前帧的基址偏移量为16字节。
10. `.cfi_offset 6, -16`:设置栈顶元素相对于rbp的偏移量为-16字节。
11. `movq %rsp, %rbp`:将栈顶指针赋值给基址指针寄存器rbp。
12. `.cfi_def_cfa_register 6`:设置当前帧的基址寄存器为rbp。
13. `movl $0, -8(%rbp)`:将0赋值给rbp-8的位置。
14. `addl $1, -8(%rbp)`:将rbp-8位置的值加1。
15. `movl -8(%rbp), %eax`:将rbp-8位置的值赋给eax寄存器。
16. `movl %eax, -4(%rbp)`:将eax寄存器的值赋给rbp-4的位置。
17. `movl $0, %eax`:将0赋值给eax寄存器。
18. `popq %rbp`:将栈顶元素弹出并赋值给基址指针寄存器rbp。

19. `.cfi_def_cfa 7, 8`:设置当前帧的基址寄存器为rbp,基址偏移量为8字节。
20. `ret`:返回到调用者。
21. `.cfi_endproc`:表示过程处理结束。
22. `.LFE0:`:这是一个局部帧缓冲区(Local Frame Buffer)的结束地址。
23. `.size    main, .-main`:设置main函数的大小为从开始到结束的距离。
24. `.ident    "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"`:这是编译器的信息。
25. `.section    .note.GNU-stack,"",@progbits`:这是一个节描述符,表示接下来的代码是程序的数据部分。

重点关注13~18行:

13行:对应c代码 int i = 0; 将0赋值给栈帧中的rbp-8的位置,由于该段代码有两个int类型局部变

           量,因此需要8个字节存储局部变量,rbp-8就是栈顶指针偏移8个字节,栈是由高到底的,

           因此是减去8,即i变量在栈帧的首地址为rbp-8

14行: 将rbp-8位置的值加1,此时 i = 1;

15行:将rbp-8位置的值给eax寄存器,eax值为1

16行:将eax中的值给rbp-4处位置,即 int a = 1;

17行:将0赋值给eax寄存器,eax一般用来存放函数的返回值

18行:栈顶元素出栈rbp-8存储i++后的i = 1,rbp-4存储a = 1。

由此可以得到 int a = ++i;中a的值为1

如果给定这样的c代码和它的汇编代码:

由上述我们可以更清晰的看到栈帧中rbp-4位置,即a的值的变化

2 i++和++i的理解

        i++的原理是先将局部变量表的变量入栈,然后局部变量本身自增,接着将栈顶的操作数保存到局部变量表(也就是覆盖自增操作),其结果是自增的结果被还原了;

        ++i的原理是上来就将局部变量表的变量自增,然后入栈,接着不管在栈顶做什么操作,都是基于自增后的值来操作的,而i++都是基于自增前的值来操作的。
        由于函数在调用过程中局部变量都是在栈中取的,因此会出现i++是先赋值再自加的现象,++i会出现直接自加后赋值的现象。

写完了,突然觉得这玩意没什么深层次的原理,就是纯粹的语言设计和编译器设计成这样。

🌈我的分享也就到此结束啦🌈
如果我的分享也能对你有帮助,那就太好了!
若有不足,还请大家多多指正,我们一起学习交流!
📢未来的富豪们:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!最后,☺祝愿大家每天有钱赚!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发狂的小花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值