编译器如何计算 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" 操作。人们可以合理地争论为什么有人会写那样的东西。好吧,优化器当然不会优化可读性。它更好的原因有几个。您可以在这里阅读它们: