您的位置:首页 > 编程语言 > C语言/C++

C/C++笔试系列--从一道IBM的笔试题看编码规范

2012-08-19 17:31 330 查看
从一道IBM的笔试题看编码规范
 
char* fun1() { cout<<"a"; return "1"; }
char* fun2() { cout<<"b"; return "1"; }
char* fun3() { cout<<"c"; return "1"; }
 
int main(int argc, char* argv[])
{
        cout<<"m"<<fun1()<<fun2()<<fun3()<<endl;
        return 0;
}
屏幕输出是多少?
cbam111
为什么不是abcm111呢?或者是ma1b1c1呢?
 
其实问题的实质上述同一个语句中多个表达式的执行顺序问题。对于<<其实**一般**是从右往左处理的,但实际上并没有规定顺序
cout<<fun1<<fun2<<fun3

operator<<(operator<<(operator<<(cout,fun1),fun2),fun3)
有点递归的感觉,第一个〈〈需要后面的结果,最后就相当于从后面开始算起
于是碰到fun()必然先输出字符,然后返回1,于是就变成了
cout<<"m"<<fun1()<<fun2()<<1;
继续往左走,直到 cout<<"m"<<1<<1<<1 ;的时候已经输出了cba,之后就是按顺序输出了m111, 所以看到的结果就是 cbam111
 
但是问题就在这里:这是个<<表达式。<<本来是位运算,但是这里cout却是来利用运算的“副作用”。所谓副作用,就是计算一个表达式的时候,除了得到它的值以外,对环境产生的影响都是副作用。
 
比如:
int a=1,b=2,c=3,d;
d=a<<b:
这一步,a<<b计算出1左移2位得到的结果。结果是4。也就是说,赋值表达式结束后,d的值变成4,其它地方都没有改变。这就是说这个<<运算没有副作用。
 
但是,cout<<"a"就不一样了。这个表达式的值我们根本就不关心。我们只关心,这个表达式“计算”完以后,"a"被输出到屏幕上了。这里“a被输出到屏幕上”就是副作用。
 
再看这个例子:
int foo(int a, int b) { return a+b; }
int bar(int a, int b) { return a-b; }
 
int a=1,b=2,c=3,d;
d=foo(a,b)+bar(b,d);
这里,foo()和bar()都没有副作用。因此,这个表达式,不论是先计算foo(a,b)的值,还是先计算bar(b,c)的值,都不会影响计算的结果。
 
但是,如果是这个例子:
int foo(int* a) { (*a)++; return *a;}  
int bar(int* a) { (*a)--; return *a;}
 
int a=5,b;
b=foo(&a)+bar(&a);
这个表达式,foo()和bar()都有副作用,所以,先计算foo(&a)还是先计算bar(&a),将直接影响到b的值。
 
假如先计算foo,再计算bar。
首先,a=5
计算foo(&a),a变成6,foo(&a)的值是6
计算bar(&a),a变成5,bar(&a)的值是5
这样,b=6+5=11
 
假如先计算bar,再计算foo。
首先,a=5
计算bar(&a),a变成4,foo(&a)的值是4
计算foo(&a),a变成5,bar(&a)的值是5
这样,b=5+4=9
 
这就造成了计算结果不一致。
 
====
 
那。。。怎么办
 
一般来说,编c/c++程序有一个纪律:一个语句中不要有两个表达式有副作用。
 
典型的这类行为包括:b=(a++)+(a++)+(a++);
这是典型的违反这条纪律的行为。每个a++都有副作用(改变a的值)。整个表达式的值跟求值顺序直接相连。
 
还有就是
char* fun() { cout<<"q"; return ""; }
cout<<"m"<<fun()<<fun()<<fun()<<endl;
每个fun()都有副作用(向屏幕上显示字符)。因此效果直接与求值顺序相关。(而整个表达式的值我们根本就不关心)。
 
======
在c/c++中,求值顺序是怎么样的?不知道。
 
C/C++的规范中,求值顺序是不规定的。这是为了给编译器以优化的空间。
比如:b=(a+2)+(a+2);,那么如果只计算一次a+2的值,而不是两次,那么计算量会大大降低。
 
因此,不要在C语言里面做这种事情:
char* fun() { cout<<"q"; return ""; }
cout<<"m"<<fun()<<fun()<<fun()<<endl;
 
要这样:
char* fun() { return "q"; }
cout<<"m"<<fun()<<fun()<<fun()<<endl; // 输出的一定是 "mqqq/n"
 
这样更好:
string fun() { return string("q"); }
cout<<"m"<<fun()<<fun()<<fun()<<endl; // 输出的一定是 "mqqq/n"
 
这样就更好了:
string f="q"; // 隐式转换
cout<<"m"<<fun()<<fun()<<fun()<<endl; // 输出的一定是 "mqqq/n"
 
由上面的一系列分析可知,当一个语句含有多个具备副作用的表达式时,程序执行的效果有时会依赖于各个表达式的执行顺序,而这种顺序有时候又是和编译器有一定的关系,尤其是优化后可能带来问题,因此良好的编程规范应该避免在同一个语句中对某个元素多次操作。
 
在实际的项目开发过程中,由于不同处理器平台编译器的差异,就出现过一样的代码在PPC侧的异常,如:
i = i++%10;
目的其实很简单,就是i递增,周期为10
期望的执行过程是
i = i%10;
i++;此时的i是上一步求余后的i,对其加1
可以从汇编代码证实:
1039:     int i = 10;
004012D3   mov         dword ptr [ebp-4],0Ah
1040:     i = i++%10;
004012DA   mov         eax,dword ptr [ebp-4]
004012DD   cdq
004012DE   mov         ecx,0Ah
004012E3   idiv        eax,ecx
004012E5   mov         dword ptr [ebp-4],edx
004012E8   mov         edx,dword ptr [ebp-4] ;重取i的值
004012EB   add         edx,1
004012EE   mov         dword ptr [ebp-4],edx
 
但是在PPC上,由于编译优化的影响,实际执行如下:
Register A = i; 将i的值先放到寄存器中
i = A %10;求余后回写道i中
A++;此处是对保存在寄存器中最初的i值进行自加运算,覆盖了上一步的求余运算
因此总体的执行效果就是i++,而并没有进行任何求余运算,问题就在于i = i++%10;语句中对同一个变量i有多次操作,不同编译器优化解释执行的顺序就可能不一样。如果我们明确将其分拆成
i = i%10;
i++;
那么无论是PC还是PPC平台,结果都是一样的
 
最终一个结论就是,不要在同一个语句中对一个资源多次操作,如果有多个表达式,则最终的执行结果应该不依赖于表达式的求值顺序。良好的编码规范可以有效的避免这些潜在的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息