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

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

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

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

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

GCC编译结果为2,3,3

TC编译结果为2,2,1

可以看到结果不一样的,于是,我把VC分别用release和debug方式编译的和TC编译的反汇编了,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才对,欢迎发表评论讨论。