zoukankan      html  css  js  c++  java
  • 深入理解C/C++ [Deep C (and C++)] (2)

    好。接着深入理解C/C++之旅。我在翻译第一篇的时候。自己是学到不不少东西,因此打算将这整个ppt翻译完成。

    请看以下的代码片段:

    [cpp]
    1. #include <stdio.h> 
    2.  
    3. void foo(void
    4.     int a; 
    5.     printf("%d ", a); 
    6.  
    7. void bar(void
    8.     int a = 42; 
    9.  
    10. int main(void
    11.     bar(); 
    12.     foo(); 


    编译执行,期待输出什么呢?

    1. $  cc  foo.c  &&  ./a.out 
    2. 42 

    你能够解释一下,为什么这样吗?

    第一个候选者:嗯?或许编译器为了重用有一个变量名称池。比方说。在bar函数中。使用而且释放了变量a,当foo函数须要一个整型变量a的时候。它将得到和bar函数中的a的同一内存区域。假设你在bar函数中又一次命名变量a,我不认为你会得到42的输出。

    你:恩。

    确定。

    第二个候选者:不错,我喜欢。

    你是不是希望我解释一下关于运行堆栈或是活动帧(activation frames, 操作代码在内存中的存放形式。譬如在某些系统上,一个函数在内存中以这样的形式存在:

    ESP

    形式參数

    局部变量

    EIP

    )?

    你:我想你已经证明了你理解这个问题的关键所在。

    可是,假设我们编译的时候,採用优化參数。或是使用别的编译器来编译,你认为会发生什么?

    候选者:假设编译优化措施參与进来。非常多事情可能会发生。比方说,bar函数可能会被忽略。由于它没有产生不论什么作用。同一时候。假设foo函数会被inline,这样就没有函数调用了,那我也不感到奇怪。

    可是由于foo函数必须对编译器可见,所以foo函数的目标文件会被创建。以便其它的目标文件链接阶段须要链接foo函数。总之,假设我使用编译优化的话,应该会得到其它不同的值。

    1. $  cc -O foo.c  &&  ./a.out 
    2. 1606415608 

    候选者:垃圾值。

    那么,请问,这段代码会输出什么?

    1. #include <stdio.h> 
    2.   
    3. void foo(void
    4.    int a = 41; 
    5.     a= a++; 
    6.    printf("%d ", a); 
    7.   
    8. int main(void
    9.    foo(); 

    第一个候选者:我没这样写过代码。

    你:不错,好习惯。

    候选者:可是我推測答案是42.

    你:为什么?

    候选者:由于没有别的可能了。

    你:确实,在我的机器上执行。确实得到了42.

    候选者:对吧,嘿嘿。

    你:可是这段代码,其实属于没有定义。

    候选者:对,我告诉过你,我没这样写过代码。

    第二个候选者登场:a会得到一个没有定义的值。

    你:我没有得到不论什么的警告信息。而且我得到了42.

    候选者:那么你须要提高你的警告级别。在经过赋值和自增以后,a的值确实没有定义。由于你违反了C/C++语言的根本原则中的一条,这条规则主要针对运行顺序(sequencing)的。

    C/C++规定,在一个序列操作中。对每个变量,你只能够更新一次。

    这里。a = a++。更新了两次,这样操作会导致a是一个没有定义的值。

    你:你的意思是,我会得到一个随意值?可是我确实得到了42.

    候选者:确实。a能够是42,41,43,0,1099,或是随意值。你的机器得到42。我一点都不感到奇怪,这里还能够得到什么?或是编译前选择42作为一个没有定义的值:)呵呵:)

     

    那么,以下这段代码呢?

    1. #include <stdio.h> 
    2.   
    3. int b(void
    4.    puts("3"); 
    5.    return 3; 
    6.   
    7. int c(void
    8.    puts("4"); 
    9.    return 4; 
    10.   
    11. int main(void
    12.    int a = b() + c(); 
    13.    printf("%d ", a); 

    第一个候选者:简单,会依次打印3,4,7.

    你:确实。

    可是也有可能是4,3,7.

    候选者:啊?运算次序也是没有定义?

    你:准确的说,这不是没有定义。而是未指定。

    候选者:无论如何。讨厌的编译器。

    我认为他应该给我们警告信息。

    你心里默念:警告什么?

    第二个候选者:在C/C++中,运算次序是未指定的,对于详细的平台。因为优化的须要。编译器能够决定运算顺序,这又和运行顺序有关。

             这段代码是符合C标准的。

    这段代码或是输出3,4,7或是输出4,3,7。这个取决于编译器。

    你心里默念:要是我的大部分同事都像你这样理解他们所使用的语言。生活会多么美好:)

    这个时候。我们会认为第二个候选者对于C语言的理解。明显深刻于第一个候选者。假设你回答以上问题,你停留在什么阶段?:)

    那么。试着看看第二个候选者的潜能?看看他究竟有多了解C/C++微笑

    能够考察一下相关的知识:

    声明和定义;

    调用约定和活动帧;

    序点;

    内存模型;

    优化;

    不同C标准之间的差别。

    这里。我们先分享序点以及不同C标准之间的差别相关的知识。

    考虑下面这段代码,将会得到什么输出?

    1. 1. 
    2. int a = 41; 
    3. a++; 
    4. printf("%d ", a); 
    5. 答案:42 
    6.   
    7. 2. 
    8. int a = 41; 
    9. a++ & printf("%d ", a); 
    10. 答案:没有定义 
    11.   
    12. 3. 
    13. int a = 41; 
    14. a++ && printf("%d ", a); 
    15. 答案:42 
    16.   
    17. 4. int a = 41; 
    18. if (a++ < 42) printf("%d ",a); 
    19. 答案:42 
    20.   
    21. 5. 
    22. int a = 41; 
    23. a = a++; 
    24. printf("%d ", a); 
    25. 答案:没有定义 

    究竟什么时候,C/C++语言会有副作用?

    序点:

    什么是序点?

    简而言之,序点就是这么一个位置。在它之前全部的副作用已经发生,在它之后的全部副作用仍未開始。而两个序点之间全部的表达式或者代码运行的顺序是没有定义的。

    序点规则1:

    在前一个序点和后一个序点之前,也就是两个序点之间,一个值最多仅仅能被写一次;



    这里,在两个序点之间。a被写了两次,因此,这样的行为属于没有定义。

    序点规则2:

    进一步说,先前的值应该是仅仅读的。以便决定要存储什么值。



    非常多开发人员会认为C语言有非常多序点。其实,C语言的序点非常少。

    这会给编译器更大的优化空间。

    接下来看看。各种C标准之间的区别:



    如今让我们回到開始那两位候选者。

    以下这段代码,会输出什么?

    1. #include <stdio.h> 
    2.   
    3. struct
    4.    int a; 
    5.    char b; 
    6.    int c; 
    7. }; 
    8.   
    9. int main(void
    10.    printf("%d ", sizeof(int)); 
    11.    printf("%d ", sizeof(char)); 
    12.    printf("%d ", 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。你接着回答这个问题吧。

    1. #include <stdio.h> 
    2.   
    3. struct
    4.    int a; 
    5.    char b; 
    6.    int c; 
    7. }; 
    8.   
    9. int main(void
    10.    printf("%zu ", sizeof(int)); 
    11.    printf("%zu ", sizeof(char)); 
    12.    printf("%zu ", sizeof(struct X)); 
    13.   

    候选者:这取决与平台,以及编译时的选项。唯一能够确定的是,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

    (未完待续)

  • 相关阅读:
    学习游戏设计
    AspectJ
    Spring AOP进行日志记录,管理 (使用Spring的拦截器功能获取对action中每个方法的调用情况,在方法调用前和调用后记录相关日志。)
    Java内存泄露测试及工具
    使用Tomcat插件开发WEB应用
    想学习建个网站?WAMP Server助你在Windows上快速搭建PHP集成环境
    UML 基础:类图
    Impala学习--Impala前端代码分析,Impala后端代码分析
    Impala学习--Impala概述,Impala系统架构
    图论--2-SAT--HDU/HDOJ 1814 Peaceful Commission
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/7271951.html
Copyright © 2011-2022 走看看