编译器如何计算 x=x+1 以及如何在汇编中表示?
How does x=x+1 is evaluated by the compiler and how is represented in assembly?
我试图了解编译器如何 "sees" 来自表达式 i=i+1 的 i+1 部分。我理解i=3就是把值3放到变量i的位置内存中。
我对 i=i+1 的猜测是,编译器期望从“=”运算符的右侧得到一个值,因此它从变量 i 的位置内存中获取值(即 3,之后赋值)并对其加 1,"i+1" 表达式(3+1=4)的最终结果作为一个值存储回变量 i 的位置存储器中。对吗?
如果是,则意味着“=”运算符右侧出现的任何 variable/combination 变量和文字将始终被存储在其中的值替换,这些值可以是 added/substracted/etc 与来自其他 variables/literals 的值(如在 x+1 表达式中),而这些计算的最终结果也将是文字值(例如:5,文字字符串等),并且也将像值一样存储在“=”运算符左侧的单个变量中。
我也很好奇这段代码在汇编中是怎么看的,这个i(i = i+1)自增的主要操作是什么;
#include <stdio.h>
int main()
{
int i = 3;
i = i + 1; // i should have the value of 4 stored back in it;
return 0;
}
这对一般情况不负责。这取决于目标平台。如果要检查程序集,可以使用带有 gcc
的 -S
参数来执行此操作。当我对你的代码这样做时,它给了我这个:
/tmp$ cat main.s
.file "main.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl , -4(%rbp)
addl , -4(%rbp)
movl [=10=], %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 9.2.1-8) 9.2.1 20190909"
.section .note.GNU-stack,"",@progbits
对这里发生的事情的简短解释。首先我们推送堆栈指针的值。这样我们就可以稍后跳回去了。
.cfi_startproc
pushq %rbp
然后我们用这段代码设置栈帧。对应于声明变量。
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
然后我们有这个。评论是我的。
movl , -4(%rbp) # i = 3;
addl , -4(%rbp) # i = i + 1;
最后,我们从主函数return
movl [=14=], %eax # Store 0 in the "return register"
popq %rbp # Restore stackpointer
.cfi_def_cfa 7, 8
ret # return
请注意,行与行之间不是一对一的关系。即使是非常简单的线条也不行。
另请注意,C 对程序的可观察行为提出了要求,而不是对生成的程序集提出了要求。因此,例如,编译器可能会删除 main 函数的整个主体,因为变量 i
未以可观察的方式使用。如果你使用优化,它就会。当我用 -O3
重新编译你的代码时,我得到了这个:
/tmp/$ cat main.s
.file "main.c"
.text
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (Debian 9.2.1-8) 9.2.1 20190909"
.section .note.GNU-stack,"",@progbits
注意从 main 中删除了多少。有趣的是,movl [=20=], %eax
已更改为 xorl %eax, %eax
。如果您考虑一下,很明显这是一个 "set zero" 操作。人们可以合理地争论为什么有人会写那样的东西。好吧,优化器当然不会优化可读性。它更好的原因有几个。您可以在这里阅读它们:
我试图了解编译器如何 "sees" 来自表达式 i=i+1 的 i+1 部分。我理解i=3就是把值3放到变量i的位置内存中。
我对 i=i+1 的猜测是,编译器期望从“=”运算符的右侧得到一个值,因此它从变量 i 的位置内存中获取值(即 3,之后赋值)并对其加 1,"i+1" 表达式(3+1=4)的最终结果作为一个值存储回变量 i 的位置存储器中。对吗?
如果是,则意味着“=”运算符右侧出现的任何 variable/combination 变量和文字将始终被存储在其中的值替换,这些值可以是 added/substracted/etc 与来自其他 variables/literals 的值(如在 x+1 表达式中),而这些计算的最终结果也将是文字值(例如:5,文字字符串等),并且也将像值一样存储在“=”运算符左侧的单个变量中。
我也很好奇这段代码在汇编中是怎么看的,这个i(i = i+1)自增的主要操作是什么;
#include <stdio.h>
int main()
{
int i = 3;
i = i + 1; // i should have the value of 4 stored back in it;
return 0;
}
这对一般情况不负责。这取决于目标平台。如果要检查程序集,可以使用带有 gcc
的 -S
参数来执行此操作。当我对你的代码这样做时,它给了我这个:
/tmp$ cat main.s
.file "main.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl , -4(%rbp)
addl , -4(%rbp)
movl [=10=], %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 9.2.1-8) 9.2.1 20190909"
.section .note.GNU-stack,"",@progbits
对这里发生的事情的简短解释。首先我们推送堆栈指针的值。这样我们就可以稍后跳回去了。
.cfi_startproc
pushq %rbp
然后我们用这段代码设置栈帧。对应于声明变量。
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
然后我们有这个。评论是我的。
movl , -4(%rbp) # i = 3;
addl , -4(%rbp) # i = i + 1;
最后,我们从主函数return
movl [=14=], %eax # Store 0 in the "return register"
popq %rbp # Restore stackpointer
.cfi_def_cfa 7, 8
ret # return
请注意,行与行之间不是一对一的关系。即使是非常简单的线条也不行。
另请注意,C 对程序的可观察行为提出了要求,而不是对生成的程序集提出了要求。因此,例如,编译器可能会删除 main 函数的整个主体,因为变量 i
未以可观察的方式使用。如果你使用优化,它就会。当我用 -O3
重新编译你的代码时,我得到了这个:
/tmp/$ cat main.s
.file "main.c"
.text
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (Debian 9.2.1-8) 9.2.1 20190909"
.section .note.GNU-stack,"",@progbits
注意从 main 中删除了多少。有趣的是,movl [=20=], %eax
已更改为 xorl %eax, %eax
。如果您考虑一下,很明显这是一个 "set zero" 操作。人们可以合理地争论为什么有人会写那样的东西。好吧,优化器当然不会优化可读性。它更好的原因有几个。您可以在这里阅读它们: