牛骨文教育服务平台(让学习变的简单)
博文笔记

[轉]深入理解C/C++ [Deep C (and C++)]

创建时间:2014-12-01 投稿人: 浏览次数:195

作者:Rockics

來源:http://blog.csdn.net/rockics/article/details/7015067


說明:譯自Deep C (and C++) by Olve Maudal and Jon Jagger,本身半桶水不到,如果哪位網友發現有錯,留言指出吧:)

 

編程是困難的,正確的使用C/C++編程尤其困難。確實,不管是C還是C++,很難看到那種良好定義並且編寫規範的代碼。為什麼專業的程序員寫出這樣的代碼?因為絕大部分程序員都沒有深刻的理解他們所使用的語言。他們對語言的把握,有時他們知道某些東西未定義或未指定,但經常不知道為何如此。這個幻燈片,我們將研究一些小的C/C++代碼片段,使用這些代碼片段,我們將討論這些偉大而充滿危險的語言的基本原則,局限性,以及設計哲學。

 

         假設你將要為你的公司招聘一名C程序言,你們公司是做嵌入式開發的,為此你要面試一些候選人。作為面試的一部分,你希望通過面試知道候選人對於C語言是否有足夠深入的認識,你可以這樣開始你們的談話:

view plain
  1. int  main ()  
  2. {  
  3.          int  a= 42;  
  4.          printf(“%d ”,a);  
  5. }  

當你嘗試去編譯鏈接運行這段代碼時候,會發生什麼?

 

一個候選者可能會這樣回答:

         你必須通過#include<stdio.h>包含頭文件,在程序的後面加上return 0; 然後編譯鏈接,運行以後將在屏幕上打印42.

         沒錯,這個答案非常正確。

 

         但是另一個候選者也許會抓住機會,藉此展示他對C語言有更深入的認識,他會這樣回答:

         你可能需要#include<stdio.h>,這個頭文件顯示地定義了函數printf(),這個程序經過編譯鏈接運行,會在標準輸出上輸出42,並且緊接著新的一行。

         然後他進一步說明:

         C++編譯器將會拒絕這段代碼,因為C++要求必須顯示定義所有的函數。然而,有一些特別的C編譯器會為printf()函數創建隱式定義,把這個文件編譯成目標文件。再跟標準庫鏈接的時候,它將尋找printf()函數的定義,以此來匹配隱式的定義。

         因此,上面這段代碼也會正常編譯、鏈接然後運行,當然你可能會得到一些警告信息。

 

         這位候選者乘勝追擊,可能還會往下說,如果是C99,返回值被定義為給運行環境指示是否運行成功,正如C++98一樣。但是對於老版本的C語言,比如說ANSI C以及K&R C,程序中的返回值將會是一些未定義的垃圾值。但是返回值通常會使用寄存器來傳遞,如果返回值的3,我一點都不感到驚訝,因為printf()函數的返回值是3,也就是輸出到標準輸出的字符個數。

         說到C標準,如果你要表明你關心C語言,你應該使用intmain (void)作為你的程序入口,因為標準就這麼說的。

         C語言中,使用void來指示函數聲明中不需要參數。如果這樣聲明函數int f(),那表明f()函數可以有任意多的參數,雖然你可能打算說明函數不需要參數,但這裡並非你意。如果你的意思是函數不需要參數,顯式的使用void,並沒有什麼壞處。

view plain
  1. int  main ( void )  
  2. {  
  3.          inta = 42;  
  4.          printf(“%d ”,a);  
  5. }  

然後,有點炫耀的意思,這位候選人接著往下說:

         如果你允許我有點點書生氣,那麼,這個程序也並不完全的符合C標準,因為C標準指出源代碼必須要以新的一行結束。像這樣:

view plain
  1. int  main ()  
  2. {  
  3.          inta = 42;  
  4.          printf(“%d ”,a);  
  5. }  
  6.    

同時別忘了顯式的聲明函數printf():

view plain
  1. #include <stdio.h>  
  2. int  main ( void )  
  3. {  
  4.          inta = 42;  
  5.          printf(“%d ”,a);  
  6. }  
  7.    

現在看起來有點像C程序了,對嗎?

 

然後,在我的機器上編譯、鏈接並運行此程序:

view plain
  1. $ cc–std=c89 –c foo.c  
  2. $ ccfoo.o  
  3. $ ./a.out  
  4. 42  
  5. $ echo $?  
  6. 3  
  7.    
  8.    
  9. $ cc–std=c99 –c foo.c  
  10. $ ccfoo.o  
  11. $ ./a.out  
  12. 42  
  13. $ echo $?  
  14. 0  

這兩名候選者有什麼區別嗎?是的,沒有什麼特別大的區別,但是你明顯對第二個候選者的答案更滿意。

 

        也許這並不是真的候選者,或許就是你的員工,呵呵。

         讓你的員工深入理解他們所使用的語言,對你的公司會有很大幫助嗎?

         讓我們看看他們對於C/C++理解的有多深……

        

view plain
  1. #include <stdio.h>  
  2.    
  3. void  foo( void )  
  4. {  
  5.    int  a = 3;  
  6.    ++a;  
  7.    printf( "%d " , a);  
  8. }  
  9.    
  10. int  main( void )  
  11. {  
  12.    foo();  
  13.    foo();  
  14.    foo();  
  15. }  

 

這兩位候選者都會是,輸出三個4.然後看這段程序:

view plain
  1. #include <stdio.h>  
  2.    
  3. void  foo( void )  
  4. {  
  5.    static  int  a = 3;  
  6.    ++a;  
  7.    printf( "%d " , a);  
  8. }  
  9.    
  10. int  main( void )  
  11. {  
  12.    foo();  
  13.    foo();  
  14.    foo();  
  15. }  
  16.    

他們會說出,輸出4,5,6.再看:

view plain
  1. #include <stdio.h>  
  2.    
  3. void  foo( void )  
  4. {  
  5.    static  int  a;  
  6.    ++a;  
  7.    printf( "%d " , a);  
  8. }  
  9.    
  10. int  main( void )  
  11. {  
  12.    foo();  
  13.    foo();  
  14.    foo();  
  15. }  
  16.    


第一個候選者發出疑問,a未定義,你會得到一些垃圾值?

你說:不,會輸出1,2,3.

候選者:為什麼?

你:因為靜態變量會被初始化未0.

 

第二個候選者會這樣來回答:

         C標准說明,靜態變量會被初始化為0,所以會輸出1,2,3.

 

再看下面的代碼片段:

view plain
  1. #include <stdio.h>  
  2.    
  3. void  foo( void )  
  4. {  
  5.    int  a;  
  6.    ++a;  
  7.    printf( "%d " , a);  
  8. }  
  9.    
  10. int  main( void )  
  11. {  
  12.    foo();  
  13.    foo();  
  14.    foo();  
  15. }  
 

第一個候選者:你會得到1,1,1.

你:為什麼你會這樣想?

候選者:因為你說他會初始化為0.

你:但這不是靜態變量。

候選者:哦,那你會得到垃圾值。

 

第二個候選者登場了,他會這樣回答:

a的值沒有定義,理論上你會得到三個垃圾值。但是實踐中,因為自動變量一般都會在運行棧中分配,三次調用foo函數的時候,a有可能存在同一內存空間,因此你會得到三個連續的值,如果你沒有進行任何編譯優化的話。

你:在我的機器上,我確實得到了1,2,3.

候選者:這一點都不奇怪。如果你運行於debug模式,運行時機制會把你的棧空間全部初始化為0.

 

 

接下來的問題,為什麼靜態變量會被初始化為0,而自動變量卻不會被初始化?

第一個候選者顯然沒有考慮過這個問題。

第二個候選者這樣回答:

把自動變量初始化為0的代價,將會增加函數調用的代價。C語言非常注重運行速度。

然而,把全局變量區初始化為0,僅僅在程序啟動時候產生成本。這也許是這個問題的主要原因。

更精確的說,C++並不把靜態變量初始化為0,他們有自己的默認值,對於原生類型(native types)來說,這意味著0。

 

再來看一段代碼:

view plain
  1. #include<stdio.h>  
  2.    
  3. static  int  a;  
  4.    
  5. void  foo( void )  
  6. {  
  7.     ++a;  
  8.     printf( "%d " , a);  
  9. }  
  10.    
  11. int  main( void )  
  12. {  
  13.     foo();  
  14.     foo();  
  15.     foo();  
  16. }  
  17.    


第一個候選者:輸出1,2,3.

你:好,為什麼?

候選者:因為a是靜態變量,會被初始化為0.

你:我同意……

候選者:cool…

 

 

這段代碼呢:

view plain
  1. #include<stdio.h>  
  2.    
  3. int  a;  
  4.    
  5. void  foo( void )  
  6. {  
  7.     ++a;  
  8.     printf( "%d " , a);  
  9. }  
  10.    
  11. int  main( void )  
  12. {  
  13.     foo();  
  14.     foo();  
  15.     foo();  
  16. }  
  17.    


第一個候選者:垃圾,垃圾,垃圾。

你:你為什麼這麼想?

候選者:難道它還會被初始化為0?

你:是的。

候選者:那他可能輸出1,2,3?

你:是的。你知道這段代碼跟前面那段代碼的區別嗎?有static那一段。

候選者:不太確定。等等,他們的區別在於私有變量(private variables)和公有變量(public variables).

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。