程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 【Deep C (and C++)】深入理解C/C++(1)

【Deep C (and C++)】深入理解C/C++(1)

編輯:C++入門知識

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

 

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

 

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

 

int main () 

         int a= 42; 
         printf(“%d\n”,a); 

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

 

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

         你必須通過#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,並沒有什麼壞處。


int main (void) 

         inta = 42; 
         printf(“%d\n”,a); 

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

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


int main () 

         inta = 42; 
         printf(“%d\n”,a); 

  

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


#include <stdio.h> 
int main (void) 

         inta = 42; 
         printf(“%d\n”,a); 

  

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

 

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


$  cc–std=c89 –c foo.c 
$  ccfoo.o 
$ ./a.out 
42 
$ echo $? 

  
  
$  cc–std=c99 –c foo.c 
$  ccfoo.o 
$ ./a.out 
42 
$ echo $? 

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

 

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

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

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

       


#include <stdio.h> 
  
void foo(void) 

   int a = 3; 
   ++a; 
   printf("%d\n", a); 

  
int main(void) 

   foo(); 
   foo(); 
   foo(); 

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


#include <stdio.h> 
  
void foo(void) 

   static int a = 3; 
   ++a; 
   printf("%d\n", a); 

  
int main(void) 

   foo(); 
   foo(); 
   foo(); 

  

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


#include <stdio.h> 
  
void foo(void) 

   static int a; 
   ++a; 
   printf("%d\n", a); 

  
int main(void) 

   foo(); 
   foo(); 
   foo(); 

  


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

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

候選者:為什麼?

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

 

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

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

 

再看下面的代碼片段:


#include <stdio.h> 
  
void foo(void) 

   int a; 
   ++a; 
   printf("%d\n", a); 

  
int main(void) 

   foo(); 
   foo(); 
   foo(); 

 

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

你:為什麼你會這樣想?

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

你:但這不是靜態變量。

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

 

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

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

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

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

 

 

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

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

第二個候選者這樣回答:

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

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

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

 

再來看一段代碼:


#include<stdio.h> 
  
static int a; 
  
void foo(void) 

    ++a; 
    printf("%d\n", a); 

  
int main(void) 

    foo(); 
    foo(); 
    foo(); 

  


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

你:好,為什麼?

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

你:我同意……

候選者:cool…

 

 

這段代碼呢:


#include<stdio.h> 
  
int a; 
  
void foo(void) 

    ++a; 
    printf("%d\n", a); 

  
int main(void) 

    foo(); 
    foo(); 
    foo(); 

  


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

你:你為什麼這麼想?

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

你:是的。

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

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

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

你:恩,差不多。

 

第二個候選者:它將打印1,2,3.變量還是靜態分配,並且被初始化為0.和前面的區別:嗯。這和鏈接器(linker)有關。這裡的變量可以被其他的編譯單元訪問,也就是說,鏈接器可以讓其他的目標文件訪問這個變量。但是如果加了static,那麼這個變量就變成該編譯單元的局部變量了,其他編譯單元不可以通過鏈接器訪問到該變量。

         你:不錯。接下來,將展示一些很不錯的玩意。靜候:)

 

摘自 Rockics的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved