printf函数输出自增变量

int main() {

    int i =3;
    printf("%d %d\n", ++i,++i);
    //printf("%d %d\n", i++,i++);
    return 0;
}

请问为什么输出5,5啊,是所有的前置加加完成后,再输出?如果删除第一个printf注释行部分输出4,3 这怎么理解

阅读 7.2k
5 个回答
#include <stdio.h>
int main() {
    int i =3;
    printf("%d %d\n", ++i,++i);
    printf("%d %d\n", i++,i++);
    return 0;
}

对应的汇编代码是:

    .file    "a.c"
    .def    ___main;    .scl    2;    .type    32;    .endef
    .section .rdata,"dr"
LC0:
    .ascii "%d %d\12\0"
    .text
    .globl    _main
    .def    _main;    .scl    2;    .type    32;    .endef
_main:
LFB6:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    $3, 28(%esp)    # 28(%esp) 保存了变量 i
    incl    28(%esp)        # 第一个++i
    incl    28(%esp)        # 第二个++i
    movl    28(%esp), %eax  # 传第2个参数
    movl    %eax, 8(%esp)
    movl    28(%esp), %eax  # 传第1个参数(i=5)
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)    # 传第0个参数(i=5)
    call    _printf         # 调用printf函数
# ------第1个printf调用到此结束, 下面开始是第二个printf调用

    movl    28(%esp), %edx  # 保存原来的i, 保证后面首先用到的是没有+1的值
    incl    28(%esp)        # i++
    movl    28(%esp), %eax  # 保存原来的i, 保证后面首先用到的是没有+1的值
    incl    28(%esp)        # i++
    movl    %edx, 8(%esp)   # 传第2个参数(i=5)
    movl    %eax, 4(%esp)   # 传第1个参数(i=6)
    movl    $LC0, (%esp)    # 传第0个参数("%d %d\n")
    call    _printf         # 调用printf函数
# ----------第2个printf调用到此结束

    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE6:
    .def    _printf;    .scl    2;    .type    32;    .endef

TZ观察得好仔细!!!

  • 首先是c语言传参, 调用带参函数的过程分为2步, 第1步传参, 第2步进入被调用函数恢复参数, 参数传递的顺序跟恢复的顺序是相反的(这个我是学过编译原理之后才知道的, 所以才会出现第二个函数调用的时候先输出6, 再输出5), 在这个简单的程序里, 传参使用的是寄存器, 如果使用栈来传递, 传参和参数恢复过程就是先压栈, 后出栈的过程. (所以在压第2个参数之后再压第1个参数, 第1个参数才能在恢复的时候首先被恢复出来)

  • 其次是i++到底什么时候执行. 从这段汇编代码来分析, 就是在i被使用过一次之后, 马上会执行i++, 那么什么时候i被使用了呢? 因为这段程序我们只有函数调用, 所以当然是传参数的时候.

  1. 根据参数传递顺序, 传第2个参数的时候, 先28(%esp), %edx保存原来的值, 作为后面传入的第2个参数, 这一步其实就已经算使用了i, 所以马上又执行i++, 也就是incl 28(%esp)

  2. 同样的, 接下来传第1个参数, 也是先保存i原来的值(这个时候i已经等于6了), 然后再i++

唔...感觉TZ问了一个神奇的问题!

别纠结了,同一行同一变量多次自增自減是未定义行为,结果取决于实现。

++i是先自增再赋值,所以两个都是5.
i++是先赋值再自增,而且C中printf的计算是从右往左,输出是从左往右,所以你会先看到4,然后是3

新手上路,请多包涵

第一个printf,我们都知道,++i,是先让i自加再用它的值,如果两个++i,放在同一行,或者说printf函数里,它会让两个i自加完以后再调用它的值。
第二个printf,我开始也想了一下,后来我想到了,在c语言里有很多赋值操作都是从右向左的,这里也不例外,我们都知道i++,是先调用值,再自加,而这里是从右边那个i++开始的,所以是4 3,如果你不相信的话,可以在第二个printf函数里再加一个i––作为验证。
我是这么想的,但不知道对不对,大家都是交流交流,希望能帮到你,如果说错了,还请大神指出来。

看了楼上的回答,这种情况应该算是未定义行为,取决于编译器实现。下面补充一些参考:

1) sequence point from wikipedia

在C与C++程序设计语言中,表达式的值依赖于它的子表达式的求值顺序。增加更多的顺序点限制了可能的求值顺序,能保证有一个一致结果。

其中的一个sequence point是函数调用时的函数入口点。函数实参的求值顺序未指定,但顺序点意味着这些实参求值的副作用在进入函数时都已经完成。表达式f(i++) + g(j++) + h(k++),调用f(), g(), h()的顺序未指定,i, j, k的自增顺序也未指定。函数调用f(a,b,c)的实参列表不是逗号运算符,a, b, and c的求值顺序未指定。

C语言的所有sequence point可以参考上面链接,但是可以发现,上面的例子所涉及的关键部分并没有包含确定的sequence point,因此行为时undefined。

gcc有个-Wsequence-point选项可以用来对违反c/c++ sequence point规则而引发的未定义行为进行警告。经过测试,gcc -Wsequence-point 1.c 编译上述代码都会发现下面的警告:

1.c:4:27: warning: operation on ‘i’ may be undefined [-Wsequence-point]
     printf("%d %d\n", ++i,++i);
                           ^

-Wsequence-point说明也可以解释楼主的疑惑:

It is not specified when between sequence points modifications to the values of objects take effect. Programs whose behavior depends on this have undefined behavior; the C and C++ standards specify that "Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.". If a program breaks these rules, the results on any particular implementation are entirely unpredictable.

2) increment and decrement operators from wikipedia

Since the increment/decrement operator modifies its operand, use of such an operand more than once within the same expression can produce undefined results. For example, in expressions such as x − ++x, it may not be clear to a user in what sequence the subtraction and increment operations should be performed. Such expressions generally invoke undefined behavior, and should be avoided.

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进