目录
函数传值 fun(i++, ++i, ++i, --i)的形式
杂言
对它简单处理,它就简单;
对它复杂处理,它就复杂;
复杂难以把控,无法预测;
可预测的程度,关系成败;
问题分析
最近在csdn上的问题区有很多关于i++的表达式的求值输出和使用有很多的问题,这边总结了各种情况。
说明下,下面的代码运行环境的编译器是gcc,大家可以在其它环境可以多试试, 比如vc上
主要有几类:
- 单个表达式输出结果,例如 printf ("%d", (i++) + (++i) )
- 多个表达式加赋值输出结果,例如 printf("%d, %d\n", i+=1, i++)
- 赋值给自己的问题,值永远不变,例如 i = i++;
- 函数传值 fun(i++, ++i, ++i, --i)的形式,printf只是个特例
单个表达式输出结果
先看如下3个运行的结果:
#include <stdio.h>
int main()
{
int i=3, j=3, k=3;
printf("%d\n",(++j)+(++j));
printf("%d\n",(++i)+(++i)+(++i));
printf("%d\n",(++k)+(++k)+(++k)+(++k));
return 0;
}
输出结果
10
16
23
结果分析
输出a的结果是10,16,23 而不是9,15,22
我们来分析一下 (++i)+(++i)+(++i) 这个表达式计算过程
按照一般的理解如果i=3,(++i)+(++i)+(++i) 是改写成 4 + 5 +6 = 15 但是答案不对
那么需要找到一种方法看它到底是怎么运行的,最直接的方法去看下编译完成后汇编语言是怎么 个执行顺序,为啥汇编能看清楚呢,那是因为程序需要编译成一条一条可执行的语句,一条语句就是两个操作数一个操作符,可以理解是最简单的一条语句不能再分解了,这样就不会有二义性。
我们来看下简单的代码看看汇编怎么样的,代码如下, 文件名叫 htest.c :
#include <stdio.h>
int main()
{
int i=3;
int a=0;
a = (++i)+(++i);
printf("%d\n", a);
return 0;
}
通过 gcc -S ./htest.c 得到一个htest.s 编译后的中间文件,里面包含了汇编指令, 如下
说明:rbp是指栈指针
subq $16, %rsp
movl $3, -4(%rbp) // int i=3; 给i赋值
movl $0, -8(%rbp) // int a=0; 给a赋值
addl $1, -4(%rbp) // ++i; 执行一次++i,i=4
addl $1, -4(%rbp) // ++i; 执行一次++i,i=5
movl -4(%rbp), %eax // i+i; 执行2次++后开始计算 5+5
addl %eax, %eax // a = i+i; 把计算结果给 a,结果是10
movl %eax, -8(%rbp) //
movl -8(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
从上面的汇编分析很容易看出来 i 是执行了2次 ++ 操作后,才开始执行 a = i+i 操作。具体的汇编指令不需要理解很深,大致看下操作指令add和mov就大概知道他们是什么意思了。
总结
通过跟踪发现对于这种单表达式 a = (++i)+(++i)+(++i) 有如下规则:我们先改写下
a = a1 + a2 +a3 其中 a1=a2=a3=++i, i=3;
- 第一步:先取出来一个完整的计算,包括一个操作符 + 号,两个操作数a1和a2,这个时候就开始计算了
- 第二步:取出a1+a2这个完整计算式后开始计算,因为是++号前置所以先加1, a1和a2都先执行加1操作, 这个时候i=5,所以 a1+a2=10
- 第三步:重复第一步,取出一个完整的表达式, 整个表达式可以改写成 a = 10 + a3, 那么先a3加执行1 之后 i=6, 所以 a = 10 + 6 =16
可以自测下面两种case,有问题可以讨论讨论
- 后置i++
- (i+=1) + (++i)
多个表达式加赋值输出结果
先看如下运行的结果
#include <stdio.h>
int main()
{
int i=3;
printf("%d %d %d\n", ++i, ++i, i+=2);
return 0;
}
输出结果:
7 7 7
结果分析:
不是4 5 7
我们来分析一下,看起来在printf里面,后面的表达式都计算完成了后才开始打印的, 来通过汇编来验证一下
subq $16, %rsp
movl $3, -4(%rbp) //i=3赋值
addl $2, -4(%rbp) //计算i+=2 i=5
addl $1, -4(%rbp) //计算++i i=6
addl $1, -4(%rbp) //计算++i i=7
movl -4(%rbp), %ecx //printf参数1 =i=7
movl -4(%rbp), %edx //printf参数2 =i=7
movl -4(%rbp), %eax //printf参数3 =i=7
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
总结
通过上面汇编的分析,printf是从右到左开始计算参数,先计算 i+=2, 在计算两个++i。所有的参数计算完了后再开始传参数,并且++操作符合我们的想法,还可以测试以下例子,++后置
printf("%d %d %d\n", i++, ++i, i+=2);
赋值给自己的问题,值永远不变
先看如下运行的结果
#include <stdio.h>
int main()
{
int i=0;
while(i<3) {
printf("%d\n", i);
i = i++; //重点分析
}
return 0;
}
输出结果:
死循环了, 并且i的值一直是0
结果分析:
看起来有 i++,怎么死循环了呢
来分析一下,为啥结果是0呢,还是老样子分析如下简单的代码的汇编
#include <stdio.h>
int main()
{
int i=0;
i = i++;
return 2;
}
汇编代码如下:
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, -4(%rbp) //i=0 赋值
movl -4(%rbp), %eax //保存i的值到eax, eax=0
leal 1(%rax), %edx
movl %edx, -4(%rbp) //i++操作 i=1
movl %eax, -4(%rbp) //eax=0 又赋值给了 i,i=0
movl $2, %eax
popq %rbp
.cfi_def_cfa 7, 8
总结:可以看出上面的运行是 有一个临时变量 t = i;之后 i++ i=1;再之后 i=t,又转回来了,i的值没有变化
函数传值 fun(i++, ++i, ++i, --i)的形式
先看下面代码
#include <stdio.h>
void func(int a, int b, int c, int d)
{
printf("%d,%d,%d,%d\n", a, b, c, d);
}
int main()
{
int i=3;
func(i++, ++i, i++, --i);
return 2;
}
结果输出:
4,5,2,5
来看下汇编代码
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $3, -4(%rbp) i=3;
subl $1, -4(%rbp) --i -> i=2
movl -4(%rbp), %edx edx = i = 2 (第三个参数 缓存i=2的值)
leal 1(%rdx), %eax eax = 1 + 2 = 3
movl %eax, -4(%rbp) i = 3
addl $1, -4(%rbp) ++i -> i = 4
movl -4(%rbp), %eax eax = 4
leal 1(%rax), %ecx ecx = 5
movl %ecx, -4(%rbp) i = 5
movl -4(%rbp), %ecx ecx = 5 (第四个参数 以最终i为参数)
movl -4(%rbp), %esi esi = 5 (第二个参数 以最终i为参数)
movl %eax, %edi edi = 4 (第一个参数 缓存i=4的值)
call _Z4funciiii
movl $2, %eax
leave
说明一下:
从上面跟踪汇编代码可以看出来,有如下规则
- 函数的参数先是从右到左开始计算参数,
- 对于i++,i--,是算到当前位置会缓存一份值,然后在立即加1或者减1,缓存的值为参数的值,比如上面的 i++,--i, 先算--i i=2,这个时候遇到i++,先缓存这个2作为参数,在加1
- 对于++i,--i,先加1,然后等所有的参数都算完后,最终的i的值作为参数
比如上面的 func(i++, ++i, i++ , --i); 四个计算完成后才传值最终是5
有点复杂,可以在多测试一下
总结
写代码的时候尽量简单,预期结果很明确的标准。遇到问题我们可以慢慢想办法去分解一下。知识尽量的全面和深入,关键时刻还是管用的
有什么不对的地方请指出,大家多讨论讨论