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

來源:互聯網
上載者:User

標籤:static   data   有一個   board   程式碼片段   repr   line   turn   位置   

好。接著深入理解C/C++之旅。我在翻譯第一篇的時候。自己是學到不不少東西,因此打算將這整個ppt翻譯完成。

請看以下的程式碼片段:

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


編譯執行,期待輸出什麼呢?

[cpp] view plaincopyprint?

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

你能夠解釋一下,為什麼這樣嗎?

第一個候選者:嗯?或許編譯器為了重用有一個變數名稱池。比方說。在bar函數中。使用而且釋放了變數a,當foo函數須要一個整型變數a的時候。它將得到和bar函數中的a的同一記憶體地區。假設你在bar函數中又一次命名變數a,我不認為你會得到42的輸出。

你:恩。

確定。

第二個候選者:不錯,我喜歡。

你是不是希望我解釋一下關於運行堆棧或是活動幀(activation frames, 作業碼在記憶體中的存放形式。譬如在某些系統上,一個函數在記憶體中以這樣的形式存在:

ESP

形式參數

局部變數

EIP

)?

你:我想你已經證明了你理解這個問題的關鍵所在。

可是,假設我們編譯的時候,採用最佳化參數。或是使用別的編譯器來編譯,你認為會發生什嗎?

候選者:假設編譯最佳化措施參與進來。非常多事情可能會發生。比方說,bar函數可能會被忽略。由於它沒有產生不論什麼作用。同一時候。假設foo函數會被inline,這樣就沒有函數調用了,那我也不感到奇怪。

可是由於foo函數必須對編譯器可見,所以foo函數的目標檔案會被建立。以便其它的目標檔案連結階段須要連結foo函數。總之,假設我使用編譯最佳化的話,應該會得到其它不同的值。

[cpp] view plaincopyprint

" href="http://blog.csdn.net/rockics/article/details/7018490#">?

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

候選者:垃圾值。

那麼,請問,這段代碼會輸出什嗎?

[cpp] view plaincopyprint?

  1. #include <stdio.h> 
  2.   
  3. void foo(void) 
  4.    int a = 41; 
  5.     a= a++; 
  6.    printf("%d\n", a); 
  7.   
  8. int main(void) 
  9.    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作為一個沒有定義的值:)呵呵:)

 

那麼,以下這段代碼呢?

[cpp] view plaincopyprint?
  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\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標準之間的差別相關的知識。

考慮下面這段代碼,將會得到什麼輸出?

[cpp] view plaincopyprint?
  1. 1. 
  2. int a = 41; 
  3. a++; 
  4. printf("%d\n", a); 
  5. 答案:42 
  6.   
  7. 2. 
  8. int a = 41; 
  9. a++ & printf("%d\n", a); 
  10. 答案:沒有定義 
  11.   
  12. 3. 
  13. int a = 41; 
  14. a++ && printf("%d\n", a); 
  15. 答案:42 
  16.   
  17. 4. int a = 41; 
  18. if (a++ < 42) printf("%d\n",a); 
  19. 答案:42 
  20.   
  21. 5. 
  22. int a = 41; 
  23. a = a++; 
  24. printf("%d\n", a); 
  25. 答案:沒有定義 
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標準之間的區別:



如今讓我們回到開始那兩位候選者。

以下這段代碼,會輸出什嗎?

[cpp] view plaincopyprint?

  1. #include <stdio.h> 
  2.   
  3. struct X 
  4.    int a; 
  5.    char b; 
  6.    int c; 
  7. }; 
  8.   
  9. int main(void) 
  10.    printf("%d\n", sizeof(int)); 
  11.    printf("%d\n", sizeof(char)); 
  12.    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。你接著回答這個問題吧。

[cpp] view plaincopyprint?

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.