一段有意思的C语代码分析

某博客里看到这么一段代码,用多个编译器编译出来都无法确定结果,因为结果不一样,代码如下:

void main()
{
    int i = 1;
    printf("%d,%d,%d",i++,++i,i);
    printf("\n\n");
}

很普通的代码,i++ 和 ++i是一样的,都是自动加1,我分别用了 VC、TC 和 GNU/Linux 下的 GCC 编译,结果如下:

VC debug 方式编译,结果为2,2,1;Release 方式编译,结果为2,3,3

GCC 编译结果为2,3,3

TC 编译结果为2,2,1

可以看到结果不一样的,于是,我把 VC 分别用 release 和 debug 方式编译的和 TC 编译的反汇编了,GNU/Linux 下 GCC 的就懒得管了,代码贴上来,不过这里说明一点,C 入栈是从右到左的顺序。

VC 下用 Debug 方式编译的反汇编:

.text:00401028                 mov     [ebp+var_4], 1
.text:0040102F                 mov     eax, [ebp+var_4]    ;eax=1
.text:00401032                 push    eax
.text:00401033                 mov     ecx, [ebp+var_4]    ;ecx=1
.text:00401036                 add     ecx, 1              ;ecx 加 1,ecx=2
.text:00401039                 mov     [ebp+var_4], ecx    ;[ebp+var_4] 现在为 2
.text:0040103C                 mov     edx, [ebp+var_4]    ;edx=2
.text:0040103F                 push    edx
.text:00401040                 mov     eax, [ebp+var_4]
.text:00401043                 mov     [ebp+var_8], eax
.text:00401046                 mov     ecx, [ebp+var_8]
.text:00401049                 push    ecx
.text:0040104A                 push    offset aDDD     ; "%d,%d,%d"
.text:0040104F                 mov     edx, [ebp+var_4]
.text:00401052                 add     edx, 1
.text:00401055                 mov     [ebp+var_4], edx
.text:00401058                 call    printf
.text:0040105D                 add     esp, 10h
.text:00401060                 push    offset asc_42201C ; "\n\n"
.text:00401065                 call    printf
.text:0040106A                 add     esp, 4
.text:0040106D                 pop     edi
.text:0040106E                 pop     esi
.text:0040106F                 pop     ebx
.text:00401070                 add     esp, 48h
.text:00401073                 cmp     ebp, esp
.text:00401075                 call    __chkesp
.text:0040107A                 mov     esp, ebp
.text:0040107C                 pop     ebp
.text:0040107D                 retn
.text:0040107D main            endp

VC 下用 Release 方式编译后的反汇编:

.text:00401000                 push    3
.text:00401002                 push    3
.text:00401004                 push    2
.text:00401006                 push    offset aDDD     ; "%d,%d,%d"
.text:0040100B                 call    sub_401020
.text:00401010                 push    offset asc_407030 ; "\n\n"
.text:00401015                 call    sub_401020
.text:0040101A                 add     esp, 14h
.text:0040101D                 retn
.text:0040101D _main           endp

TC 编译后的反汇编:

seg000:01FA                 push    bp
seg000:01FB                 mov     bp, sp  ;sp 和 bp平等
seg000:01FD                 push    si
seg000:01FE                 mov     si, 1   ;si=1
seg000:0201                 push    si
seg000:0202                 inc     si      ;自动加 1
seg000:0203                 mov     ax, si    ;ax=2
seg000:0205                 push    ax
seg000:0206                 mov     ax, si
seg000:0208                 inc     si    ;自动加 1
seg000:0209                 push    ax   ;ax=2
seg000:020A                 mov     ax, 194h
seg000:020D                 push    ax              ; format
seg000:020E                 call    _printf
seg000:0211                 add     sp, 8
seg000:0214                 mov     ax, 19Dh
seg000:0217                 push    ax              ; format
seg000:0218                 call    _printf
seg000:021B                 pop     cx
seg000:021C                 pop     si
seg000:021D                 pop     bp
seg000:021E                 retn
seg000:021E _main           endp

Release 方式编译,代码会经过优化的,在编译生成时,计算出了三个 i 的值,从上面 Release 方式编译后的反汇编代码中可以看出,三个 i 的值是直接将计算好的压入栈。编译出了逻辑问题了?编译时i成了一个累加器了,它首先给 i 赋值,i=1,此时 i 的值为1,当遇到第一个 i++ 时,i=2 了,++i 时,i 又自动加 1,i=3,最后一个i就是相加后的 i,i=3,所以它编译的输出结果是 2,3,3。

i 值应该是不变化的,也就是 int i=1,i 就等于1,当遇到第一个 i++ 时,i=2,当遇到第二个 ++i 时,i=2,最后一个 i,i 应该是 i=1,i 的值不会随着数位相加而值变化,最后结果应该是 2,2,1 才对,欢迎发表评论讨论。