深入理解C/C++ [Deep C (and C++)] (2)
好。接着深入理解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();
- }
#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
$ 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
$ 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();
- }
#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);
- }
#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);
- 答案:没有定义
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",
sizeof(char)); - printf("%d\n", sizeof(struct X));
- }
#include <stdio.h> struct X
{
int a;
char b;
int c;
}; int main(void)
{
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(struct X));
}
第一个候选者:它将打印出4,1,12.
你:确实,在我的机器上得到了这个结果。
候选者:当然。
由于sizeof返回字节数。在32位机器上,C语言的int类型是32位,或是4个字节。char类型是一个字节长度。
在struct中,本例会以4字节来对齐。
你:好。
你心里默念:do you want another ice cream?(不知道有什么特别情绪)
第二个候选者:恩。首先。先完好一下代码。sizeof的返回值类型是site_t,并不总是与int类型一样。
因此,printf中的输出格式%d,不是一个非常好的说明符。
你:好。
那么,应该使用什么格式说明符?
候选者:这有点复杂。site_t是一个无符号整型数,在32位机器上。它一般是一个无符号的int类型的数。可是在64位机器上,它一般是一个无符号的long类型的数。
然而,在C99中。针对site_t类型,指定了一个新的说明符,所以。%zu会是一个不多的选择。
你:好。那我们先完好这个说明符的bug。你接着回答这个问题吧。
- #include <stdio.h>
- struct X
- {
- int a;
- char b;
- int c;
- };
- int main(void)
- {
- printf("%zu\n", sizeof(int));
- printf("%zu\n",
sizeof(char)); - printf("%zu\n", sizeof(struct X));
- }
#include <stdio.h> struct X
{
int a;
char b;
int c;
}; int main(void)
{
printf("%zu\n", sizeof(int));
printf("%zu\n", sizeof(char));
printf("%zu\n", sizeof(struct X));
}
候选者:这取决与平台,以及编译时的选项。唯一能够确定的是,sizeof(char)是1.你要如果在64位机器上执行吗?
你:是的。我有一台64位的机器,执行在32位兼容模式下。
候选者:那么因为字节对齐的原因。我认为答案应该是4,1,12.当然,这也取决于你的编译选项參数,它可能是4,1,9.假设你在使用gcc编译的时候,加上-fpack-struct,来明白要求编译器压缩struct的话。
你:在我的机器上确实得到了4,1,12。为什么是12呢?
候选者:工作在字节不正确齐的情况下。代价很昂贵。因此编译器会优化数据的存放,使得每个数据域都以字边界開始存放。struct的存放也会考虑字节对齐的情况。
你:为什么工作在字节不正确齐的情况下,代价会非常昂贵?
候选者:大多数处理器的指令集都在从内存到cpu拷贝一个字长的数据方面做了优化。假设你须要改变一个横跨字边界的值,你须要读取两个字,屏蔽掉其它值,然后改变再写回。
可能慢了10不止。
记住,C语言非常注意执行速度。
你:假设我得struct上加一个char d。会怎么样?
候选者:假设你把char d加在struct的后面。我估计sizeof(struct X)会是16.由于,假设你得到一个长度为13字节的结构体,貌似不是一个非常有效的长度。可是,假设你把char d加在char b的后面,那么12会是一个更为合理的答案。
你:为什么编译器不重排结构体中的数据顺序。以便更好的优化内存使用和执行速度?
候选者:确实有一些语言这样做了,可是C/C++没有这样做。
你:假设我在结构体的后面加上char *d。会怎么样?
候选者:你刚才说你的执行时环境是64位。因此一个指针的长度的8个字节。或许struct的长度是20?可是还有一种可能是,64位的指针须要在在效率上对齐,因此,代码可能会输出4,1,24?
你:不错。我不关心在我的机器上会得到什么结果。可是我喜欢你的观点以及洞察力J
(未完待续)
深入理解C/C++ [Deep C (and C++)] (2)的更多相关文章
- 深入理解C/C++ [Deep C (and C++)]
编程是困难的,正确的使用C/C++编程尤其困难.确实,不管是C还是C++,很难看到那种良好定义并且编写规范的代码.为什么专业的程序员写出这样的代码?因为绝大部分程序员都没有深刻的理解他们所使用的语言. ...
- 【深度学习Deep Learning】资料大全
最近在学深度学习相关的东西,在网上搜集到了一些不错的资料,现在汇总一下: Free Online Books by Yoshua Bengio, Ian Goodfellow and Aaron C ...
- 机器学习(Machine Learning)&深度学习(Deep Learning)资料(Chapter 2)
##机器学习(Machine Learning)&深度学习(Deep Learning)资料(Chapter 2)---#####注:机器学习资料[篇目一](https://github.co ...
- [Thu, 9 Jul 2015 ~ Tue, 14 Jul 2015] Deep Learning in arxiv
这一期的神作论文有蛮多的,都很有意思. Feature Representation In ConvolutionalNeural Networks 该论文中论述了在某种CNN结构下,是否有准确率较高 ...
- Xamarin教程索引页
持续更新中-- 近期学习处理Xamarin.Android动画内容 Xamarin指南 -- 官网教程翻译 Xamarin跨平台开发 Xamarin Workbooks Xamarin Workboo ...
- 7月清北学堂培训 Day 3
今天是丁明朔老师的讲授~ 数据结构 绪论 下面是天天见的: 栈,队列: 堆: 并查集: 树状数组: 线段树: 平衡树: 下面是不常见的: 主席树: 树链剖分: 树套树: 下面是清北学堂课程表里的: S ...
- Deep Learning 23:dropout理解_之读论文“Improving neural networks by preventing co-adaptation of feature detectors”
理论知识:Deep learning:四十一(Dropout简单理解).深度学习(二十二)Dropout浅层理解与实现.“Improving neural networks by preventing ...
- Deep learning:五十(Deconvolution Network简单理解)
深度网络结构是由多个单层网络叠加而成的,而常见的单层网络按照编码解码情况可以分为下面3类: 既有encoder部分也有decoder部分:比如常见的RBM系列(由RBM可构成的DBM, DBN等),a ...
- Deep learning:四十九(RNN-RBM简单理解)
前言: 本文主要是bengio的deep learning tutorial教程主页中最后一个sample:rnn-rbm in polyphonic music. 即用RNN-RBM来model复调 ...
随机推荐
- selenium TestNG基本注释和属性
TestNG注释详解 suite 属性说明: @name: suite 的名称,必须参数@junit:是否以Junit 模式运行,可选值(true | false),默认"false&quo ...
- 用 python 来操作 docx, xlsx 格式文件(一)(使用 xlsxwriter 库操作xlsx格式文件)
需要从数据库读取日志生成相应的 docx,xlsx 文件做相应的记录 所以自然要用到docx, xlsxwriter 库 但是这些库的应用场景非常广泛,任何需要对 word,excel 文件执行重复性 ...
- Nginx网站根目录更改及导致403 forbidden的问题解决
最近因为工作需要,要将Nginx网站根目录更改下,通过网上的一些教程更改后,但发现测试的时候一直提示403 forbidden错误,后台通过一个朋友的提示也解决了,所以现在将详细的步骤分享给大家,有需 ...
- Go简单的Goroutine示例
最简单的,接下来,会是竞争,加锁... package main import ( "fmt" "runtime" "sync" ) var ...
- [BZOJ2653]middle 主席树+二分
2653: middle Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 2042 Solved: 1123[Submit][Status][Disc ...
- SecureCRT 的上传和下载操作
在网上找了两篇文章,分别关于ftp和ssh的上传下载,如果有好的大家可以留言分享,不胜感谢~ 因为关于ftp的比较少,就copy上面,本人并没有验证.关于ssh用sr和sz发现一条错误,而且网上也有解 ...
- AMQ学习笔记 - 04. 消息选择器
概述 消息选择器使用类似于SQL语法,为Consumer指定基于Message属性的筛选条件. 消息选择器 发送的时候,给消息添加一些属性:在接收的时候,根据属性进行过滤. API javax.jms ...
- Oracle 后台进程 详细说明
一. 进程概述 先来看一下Oracle 11g 的架构图. 看起来比较模糊,我已经上传到了csdn 的下载. 是个pdf 文件, 2m 多. 那个看起来比较清楚. 也对每个进程做了解释. 下载地址:O ...
- [UOJ300]吉夫特
直接上lucas定理,可以得到$\binom nm=1$等价于$m$是$n$的子集(二进制) 因为数字两两不同,所以设$f_i$表示以$i$开头的满足要求的序列有多少个,转移就是$f_i\gets f ...
- 【dfs】bzoj3563 DZY Loves Chinese
因为我们可以通过把某一行读到末尾来获取真正的K,所以把它和假K异或之后就是之前联通的次数(异或的逆运算为其本身).最后一次的暴力一下. #include<cstdio> #include& ...