眾所皆知,C語言支援pass-by-value與pass-by-pointer兩種傳遞引數給參數的機制。而C++除了支援C語言的兩種機制之外,還支援pass-by-reference傳遞引數機制。
程式語言一般將函式/程序呼叫時的引數傳遞機制區分為call-by-value與call-by-address兩大類,通常call-by-address是不可或缺的機制,因為只要支援call-by-address,就可以透過一些額外的變數完成call-by-value的效果,但若只支援call-by-value而想要達到類似於call-by-address的效果,那麼除非這個value必須是一個位址。
在不考慮全域變數的狀況下(全域變數會有邊際效應的問題),call-by-address機制是程式語言不可或缺的功能,因為函式/程序返回時只能回傳一個值,因此,當呼叫端與被呼叫端需要兩個以上的變數進行溝通時,就勢必需要採取call-by-address的機制來完成。有些語言只支援了call-by-value機制,因此,正如同上述所言,它必須以call-by-value的機制來模擬call-by-address的效果,而方法則是將此value設定為address,例如C語言正是如此。
為何我們要特別提出「C語言只支援call-by-value」這個議題呢?
這是因為有些人誤把C語言的pass-by-pointer歸類為程式語言的call-by-address機制,這是錯誤的觀念,並且很多人都犯了這個錯誤。
事實上,C語言的pass-by-pointer仍屬於call-by-value,只不過,這個value恰好是一個address,因為pointer之值原本就是一個位址。

有些人或許不認同「C語言只支援call-by-value」這個說法,因此,我們將於本篇技術專欄中,證明這一個事實。我們將從兩方面分別來證明此一事實。

 
     
 

方式一:引經據典來證明

 
 

以下是C語言發明人Brian W. Kernighan and Dennis M. Ritchie在其著作The C programming Language第36頁中關於引數機制的說明。在該段文字中,發明人已經清楚說明了C語言只支援以值來傳遞引數的機制。
One aspect of C functions may be unfamiliar to programmers who are used to some other languages, particularly Fortran. In C, all function arguments are passed "by value''. This means that the called function is given the values of its arguments in temporary variables rather than the originals. This leads to some different properties than are seen with "call by reference"' languages like Fortran or with var parameters in Pascal, in which the called routine has access to the original argument, not a local copy.

 
     
 

方式二:以傳遞常數來證明

 
  我們首先從C++之pass-by-reference開始說明,C++的pass-by-reference屬於call-by-address是眾所皆知且較無爭議的。當我們試著以pass-by-reference傳遞一個常數給被呼叫端時,編譯器就會顯示其為不合法。  
 
// test.cpp
#include 
#include 

void func1(int& x,int& y)
{
  int temp;
  temp=x;
  x=y;
  y=temp;
}

int main(void)
{
  int a=10,b=20; 
  const int c=5;
  func1(a,b);
  func1(c,b);          /* 不合法,因為c為常數  */
  func1(a,2);          /* 不合法,因為2為常數  */
  system("pause"); 
  return 0;
}
 
     
 

上述C++程式交由Dev-C++/GCC編譯時,會顯示下列錯誤訊息:

 
 
\test.cpp invalid initialization of reference of type 'int&' from expression of
type 'const int' \test.cpp in passing argument 1 of 'void func1(int&, int&)' \test.cpp invalid initialization of non-const reference of type 'int&' from a
temporary of type 'int' \test.cpp in passing argument 2 of 'void func1(int&, int&)'
 
     
 

上述的錯誤訊息,很明顯地說明,當使用pass-by-reference傳遞引數時,不可以將引數設定為常數。這是很合理的,因為pass-by-reference屬於call-by-address的一種,而如果我們允許在call-by-address中傳遞常數,則常數內容將可能會被改變,這會使得程式中所有使用該常數的地方都受到影響。因此,編譯器不允許如此作。
那麼C語言的pass-by-pointer是否屬於call-by-address呢?答案當然是否定的,舉例來說,我們可以採用pass-by-pointer傳遞一個常數指標給被呼叫端,因此,它不屬於call-by-address(再次強調,call-by-address不允許傳遞常數),而最明顯的例子為傳遞陣列,因為陣列正是一個常數指標。

除了陣列之外,我們也可以透過下面這個範例,觀察pass-by-pointer確實可以傳送常數給被呼叫端。

 
 
/* test.c pass-by-pointer */
#include 
#include 

void func1(int *x,int *y)
{
  int temp;
  temp=*x;
  *x=*y;
  *y=temp;
}

int main(void)
{
  int p=10,q=20,r=5;
  int *a=&p;
  int *b=&q; 
  const int *c=&r;
  func1(a,b);
  func1(c,b);
  printf("%d,%d",*c,*b);
  system("pause"); 
  return 0;
}
 
     
 

上述範例中的c為一個常數指標,其指標所保存的記憶體位址是固定的,但該記憶體位址的內容是可以變動的,換句話說,我們無法將c指向其他位址,因此,c與陣列名稱具有相同的效果。而上述這個範例可以通過編譯與執行。這說明了pass-by-pointer允許傳遞常數(這個常數是一個指標,也就是一個固定不變的記憶體位址)。這說明了C語言只支援call-by-value,並且在pass-by-pointer時,採用的方式是將address當作value來傳遞,所以可以模擬call-by-address的效果,但它絕不是call-by-address,因為call-by-address不允許傳遞常數。

相關更進一步的技術說明,請見C語言初學指引第四版第七章。
 
     
 

作者介紹:

作者陳錦輝著有數十本資訊圖書,除在程式語言方面著有初學指引系列外,並涉獵物件導向、資料結構、編譯器等專業領域。其初學指引系列著重在引導初學者進入程式設計的世界,常配合大量圖示及範例說明,以祈讀者能夠透過自學而成為初級的程式設計師,若能搭配教師授課內容的補充說明,相信更能讓學生無懼於程式設計的困難,進而引發對於程式設計的興趣。