再C++函數傳遞有三種方式 Call by value 、Call by address、Call by reference,而C只有前面兩種,C語言沒有Call by reference。既然傳入的方式有那些,當然我們也可利用那些方式將函數處理後的結果回傳回去給函數呼叫者(funcation caller)。回傳值是最簡單的方式,將運算後的結果回傳回去,然後funcation caller可以決定是否要繼續處理該回傳值,而該函數要回傳的方式可以是運算式(express)、常數、變數,比起回傳址(return by address)與回傳參考(return by reference)來的彈性多了,但是回傳值只能回傳一個值,所以才會有回傳址(return by address),而回傳參考是在類別與結構上才看的出它的好處,例如你常用的cout為什麼可以 cout << a << b 呢?因為他回傳的是物件的參考,這在類別與結構上常用的方式,有興趣的的可以去研究研究。
今天要來研究是的是回傳型態的問題,之前看了一本書,書中有一章節寫著函數回傳變數位址,然後就舉個例子,如何透過函數回傳位址(addree),於是就寫了如下面的function:
int *RetAddr()
{
int a = 100;
int *p=&a;
return p;
}
這也許是它想要教導如何透過函數回傳位址(return by address)吧,但卻未提出警告這樣寫的話有什麼後果,現在有很多書都會告知含數不可回傳區域變數的位址與參考值,因為當你return 該值後,函數遇到了 } 結束函數,而區域變數也就被銷毀了,值就會變成垃圾,到時候要除錯的話還真不知道哪邊出暸問題咧。
好久以前就做過這個實驗了,但是沒有把結果記錄下來。因為基本上不會回傳區域變數的位址與參考值給function calller,故也不常遇到這樣的問題,倒是剛學程式語言時常會發:P
前幾天寫了一個驗證的程式如下:
//--------------------------------------------------------------------------------------------------------------------------------------
#ifdef __cplusplus
#include <iostream>
using namespace std;
#else
#include <stdio.h>
#include <stdlib.h>
#endif
#ifdef __cplusplus
// return by reference only for C++
int & ReturnByRef()
{
int a = 10;
return a;
}
#endif
int * ReturnByAddr()
{
int a = 10;
#ifdef __cplusplus
cout << "Local Addr : " << (int *)&a << endl;
#else
printf("Local Addr : %#X\n",&a);
#endif
return &a;
}
int main(int argc, char *argv[])
{
int *a = ReturnByAddr();
#ifdef __cplusplus
int b = ReturnByRef();
#endif
#ifdef __cplusplus
cout << "Return by address...\n";
cout << "*a= " << *a << endl;
cout << "*a= " << *a << endl;
cout << " a= " << a << endl;
cout << "Return by reference...\n";
cout << " b= " << b << endl;
cout << " b= " << b << endl;
cout << " b= " << b << endl;
#else
printf("Return by address...\n");
printf("a=%d\n",*a);
printf("a=%d\n",*a);
printf("a=%#X\n",a);
#endif
system("PAUSE");
return EXIT_SUCCESS;
}
//--------------------------------------------------------------------------------------------------------------------------------------------
我分別在Dev C++、VS2005、Turbo C++ 3.0上跑出的結果,如下面的圖示:
1.利用 VS 2005 C 編譯器,在編譯的過程編譯器就會提出警告,如下圖:
![]()
2.以Debug 模式編譯完後跑出的結果,可以看出結果並非我們所想。
![]()
3.透過Release模式編譯後跑出來的結果,夭壽ㄛ~答案盡然是對的,這也是為甚麼常會遇到release 版本與debug版本行為模式有時候會不一樣,所以當debug模式沒有問題,並不代表release模式就沒有問題,還是需要在測試整個流程(Test case)。Release後的版本做了一些最佳化以及移除了一些除錯訊息,結果程式跑出來卻是正確的答案。=.=-!!!
![]()
4.利用 VS 2005 C++ 編譯器,在編譯的過程編譯器就會提出警告,如下圖:
![]()
5.透過VS 2005 C++編譯器編譯出debug版本執行的結果,但是會發現return by address的結果如我們所料,答案是錯的,但是return by reference 卻是對的 =.=!!!!。
![]()
6.這次將其編譯成release 版本,變成return by address 變成正常,與使用VS 2005 C編譯器的結果一致,而retunr by referece保持與debug版本的結果一樣。@.@
![]()
7.這次換個編譯器,透過Dev C++的gcc編譯器(C的編譯器),如VS2005一樣,在編譯的時候也提出了警告訊息。
![]()
8.透過DEV C++編譯出debug版本,也跟VS2005相同的結果,出來的資料也是錯誤的。
![]()
9.將DevC++的除錯訊息全部關掉,然後選擇Best Optimization選項,跑出來的結果與VS2005 release版本是一樣的,都顯示出正確的值。但只有選擇Best Optimization才會有這樣的情況,如果選擇其他最佳化的選項則出來的結果還是錯誤的。
![]()
10.這次利用DevC++的 g++(C++的編譯器)編譯器編譯debug版本,在編譯時也出現了警告訊息。
![]()
11.DevC++ Debug版本,這次出來的結果還是與VS2005一樣,return by address的值是錯的,但是return by reference 是正確的結果。
![]()
12.與gcc設定的一樣,將除錯資訊全部拿掉,將最佳化選擇Best Optimization,顯現出來的結果與VS2005一樣,不管是return by address 與 return by reference答案都正確。
![]()
13.這次透過TC++ 3.0編譯,TC 的C++編譯器上面return by reference無法編譯成功,所以移除不測試。
![]()
14.用TC++的C編譯器編譯出來,結果如預期是錯誤的。
![]()
15.透過TC++的C++編譯器執行的結果也是如預期的顯示錯誤的訊息,因為懶的設定所以沒有release版本的資料: P
[結論]
這是測驗的結果,不管是不是有最佳化,return by reference在我比較常用的編譯器上都可以顯示正確的結果,實在有點不解,也許就像最佳化後return by address也顯示了正確結果,也許該設定些什麼參數後就會跑出如書上所寫的的到的是無用的資料。雖然return by reference有點出乎意料,但是return by address的確有上面所提到的問題,所以應該避免讓函數回傳區域變數的參考與位址,可攜性與安全性都會比較高。
[參考鏈結]
Returning values by value, reference, and address