【Deep C (and C++)】深入理解C/C++(2)
译自Deep C (and C++) by Olve Maudal and Jon Jagger,本身半桶水不到,如果哪位网友发现有错,留言指出吧:)
好,接着深入理解C/C++之旅。我在翻译第一篇的时候,自己是学到不不少东西,因此打算将这整个ppt翻译完毕。
请看下面的代码片段:
#include <stdio.h>
void foo(void)
{
int a;
printf("%d\n", a);
}
void bar(void)
{
int a = 42;
}
int main(void)
{
bar();
foo();
}
编译运行,期待输出什么呢?
$ cc foo.c && ./a.out
42
你可以解释一下,为什么这样吗?
第一个候选者:嗯?也许编译器为了重用有一个变量名称池。比如说,在bar函数中,使用并且释放了变量a,当foo函数需要一个整型变量a的时候,它将得到和bar函数中的a的同一内存区域。如果你在bar函数中重新命名变量a,我不觉得你会得到42的输出。
你:恩。确定。。。
第二个候选者:不错,我喜欢。你是不是希望我解释一下关于执行堆栈或是活动帧(activation frames, 操作代码在内存中的存放形式,譬如在某些系统上,一个函数在内存中以这种形式存在:
ESP
形式参数
局部变量
EIP
)?
你:我想你已经证明了你理解这个问题的关键所在。但是,如果我们编译的时候,采用优化参数,或是使用别的编译器来编译,你觉得会发生什么?
候选者:如果编译优化措施参与进来,很多事情可能会发生,比如说,bar函数可能会被忽略,因为它没有产生任何作用。同时,如果foo函数会被inline,这样就没有函数调用了,那我也不感到奇怪。但是由于foo函数必须对编译器可见,所以foo函数的目标文件会被创建,以便其他的目标文件链接阶段需要链接foo函数。总之,如果我使用编译优化的话,应该会得到其他不同的值。
$ cc -O foo.c && ./a.out
1606415608
候选者:垃圾值。
那么,请问,这段代码会输出什么?
#include <stdio.h>
void foo(void)
{
int a = 41;
a= a++;
printf("%d\n", a);
}
int main(void)
{
foo();
}
第一个候选者:我没这样写过代码。
你:不错,好习惯。
候选者:但是我猜测答案是42.
你:为什么?
候选者:因为没有别的可能了。
你:确实,在我的机器上运行,确实得到了42.
候选者:对吧,嘿嘿。
你:但是这段代码,事实上属于未定义。
候选者:对,我告诉过你,我没这样写过代码。
第二个候选者登场:a会得到一个未定义的值。
你:我没有得到任何的警告信息,并且我得到了42.
候选者:那么你需要提高你的警告级别。在经过赋值和自增以后,a的值确实未定义,因为你违反了C/C++语言的根本原则中的一条,这条规则主要针对执行顺序(sequencing)的。C/C++规定,在一个序列操作中,对每一个变量,你仅仅可以更新一次。这里,a = a++;更新了两次,这样操作会导致a是一个未定义的值。
你:你的意思是,我会得到一个任意值?但是我确实得到了42.
候选者:确实,a可以是42,41,43,0,1099,或是任意值。你的机器得到42,我一点都不感到奇怪,这里还可以得到什么?或是编译前选择42作为一个未定义的值:)呵呵:)
那么,下面这段代码呢?
#include <stdio.h>
int b(void)
{
puts("3");
return 3;
}
int c(void)
{
puts("4");
return 4;
}
int main(void)
{
int a = b() + c();
printf("%d\n", a);
}
第一个候选者:简单,会依次打印3,4,7.
你:确实。但是也有可能是4,3,7.
候选者:啊?运算次序也是未定义?
你:准确的说,这不是未定义,而是未指定。
候选者:不管怎样,讨厌的编译器。我觉得他应该给我们警告信息。
你心里默念:警告什么?
第二个候选者:在C/C++中,运算次序是未指定的,对于具体的平台,由于优化的需要,编译器可以决定运算顺序,这又和执行顺序有关。
这段代码是符合C标准的。这段代码或是输出3,4,7或是输出4,3,7,这个取决于编译器。
你心里默念:要是我的大部分同事都像你这样理解他们所使用的语言,生活会多么美好:)
这个时候,我们会觉得第二个候选者对于C语言的理解,明显深刻于第一个候选者。如果你回答以上问题,你停留在什么阶段?:)
那么,试着看看第二个候选者的潜能?看看他到底有多了解C/C++
可以考察一下相关的知识:
声明和定义;
调用约定和活动帧;
序点;
内存模型;
优化;
不同C标准之间的区别;
这里,我们先分享序点以及不同C标准之间的区别相关的知识。
考虑以下这段代码,将会得到什么输出?
1.
int a = 41;
a++;
printf("%d\n", a);
答案:42
2.
int a = 41;
a++ & printf("%d\n", a);
答案:未定义
3.
int a = 41;
a++ && printf("%d\n", a);
答案:42
4. int a = 41;
if (a++ < 42) printf("%d\n",a);
答案:42
5.
int a = 41;
a = a++;
printf("%d\n", a);
答案:未定义
到底什么时候,C/C++语言会有副作用?
序点:
什么是序点?
简而言之,序点就是这么一个位置,在它之前所有的副作用已经发生,在它之后的所有副作用仍未开始,而两个序点之间所有的表达式或者代码执行的顺序是未定义的!
序点规则1:
在前一个序点和后一个序点之前,也就是两个序点之间,一个值最多只能被写一次;
这里,在两个序点之间,a被写了两次,因此,这种行为属于未定义。
序点规则2:
进一步说,先前的值应该是只读的,以便决定要存储什么值。
很多开发者会觉得C语言有很多序点,事实上,C语言的序点非常少。这会给编译器更大的优化空间。
接下来看看,各种C标准之间的差别:
现在让我们回到开始那两位候选者。
下面这段代码,会输出什么?
#include <stdio.h>
struct X
{
int a;
char b;
int c;
};
int main(void)
{
printf("%d\n", sizeof(int));
printf("%d\n&q
补充:软件开发 , C++ ,