(筆記) struct對function可以call by value嗎?可以return一個struct嗎? (C/C++)

 
Abstract
C在傳遞較大型資料結構進function時,如array、string、struct時,都建議使用pointer的call by address,是否也能使用call by value呢?

Introduction
使用環境:Visual Studio 2010 / Visual C++ 10.0, Turbo C 2.0

C在傳遞資料進function時,就只有兩招,一招是call by value,一招是call by address(實際上也是一種call by value,只是它copy的是value的address,而不是value本身),一些較小型的型別如int、double,我們會使用call by value配合return,當然使用call by address亦可;而一些較大的型別,如string、array、struct,我們會使用call by address的方式,也就是只把pointer copy進stack,而不需將整個資料copy進stack,這樣比較有效率。

昨天好友Roger問我可以將struct以call by value的方式傳進function,並且return一個struct嗎?

在實驗之前,我們先用既有的認知做推理:

 
  
1 .根據以往的經驗,struct都是以call by address的方式,所以不確定C compiler是否能接受struct call by value的寫法。(須實驗)

2 .就算C compiler能接受語法,是真的call by value?還是只是call by address的syntax sugar? (須實驗)

3 .就算C compiler能struct call by value,效率一定遠比struct call by address差,因為struct通常都很大,且需整個struct copy進stack,再整個struct從stack內copy出來。(這點可以確定)

接下來,我們來作實驗:

首先,我們來看正統的寫法,也就是struct call by address

struct_call_by_address.c / C

 
  
1 /*
2 (C) OOMusou 2011 http://oomusou.cnblogs.com
3
4 Filename : struct_call_by_address.c
5 Compiler : Visual C++ 10.0
6 Description : struct call by address in C
7 Release : 02/18/2011 1.0
8   */
9
10 #include < stdio.h >
11 #include < string .h > // strcpy()
12  
13 typedef struct {
14 int no;
15 char name[ 10 ];
16 } student, * pstudent;
17
18 pstudent struct_call_by_address(pstudent pboy) {
19 pboy -> no = 10 ;
20 strcpy(pboy -> name, " oomusou " );
21 printf( " in function:\n " );
22 printf( " no=%d, name=%s\n " , pboy -> no, pboy -> name);
23 return pboy;
24 }
25
26   int main() {
27 student boy = { 20 , " John " };
28 pstudent pboy = 0 ;
29
30 printf( " before function\n " );
31 printf( " no=%d, name=%s\n " , boy.no, boy.name);
32 pboy = struct_call_by_address( & boy);
33 printf( " after function\n " );
34 printf( " no=%d, name=%s\n " , boy.no, boy.name);
35 printf( " no=%d, name=%s\n " , pboy -> no, pboy -> name);
36 }

執行結果

 
  
before function
no
= 20 , name = John
in function:
no
= 10 , name = oomusou
after function
no
= 10 , name = oomusou
no
= 10 , name = oomusou

13行

 
  
typedef struct {
int no;
char name[ 10 ];
} student,
* pstudent;

使用typedef重新定義struct型別student,並且順便定義pointer版本的pstudent,因為struct傳進function時一定用的到pointer,實務上都是兩個型別一起宣告在header file(*.h)中。

27行

 
  
student boy = { 20 , " John " };

宣告boy變數,並且一起做初始化。

28行

 
  
pstudent pboy = 0 ;

宣告pboy這個指向struct的pointer,並且馬上指定為0(或者NULL)避免wild pointer的發生,這是一個好習慣。

18行

 
  
pstudent struct_call_by_address(pstudent pboy) {

定義struct_call_by_address() function,注意傳進去的是pointer版本的struct,回傳的也是pointer版本的struct。

30行

 
  
printf( " before function\n " );
printf(
" no=%d, name=%s\n " , boy.no, boy.name);

結果為

 
  
before function
no
= 20 , name = John

先印出執行function前的值,結果為27行剛出始化的27與John,符合預期。

32行

 
  
pboy = struct_pass_by_address( & boy);

呼叫struct_pass_by_address() function,由於要傳進去的是pointer,所以必須&boy;因為傳回的也是pointer,所以使用pboy。

19行

 
  
pboy -> no = 10 ;
strcpy(pboy
-> name, " oomusou " );

實際去改變struct值,因為傳進function的為pointer pboy,所以要用->,這是C的語法規定;另外C的字串也規定要用strcyp(),無法用pboy->name = "oomusou"這種直覺的語法(C++可以),既然在這裡用C,就只好認了。

21行

 
  
printf( " in function:\n " );
printf(
" no=%d, name=%s\n " , pboy -> no, pboy -> name);

結果為

 
  
in function:
no
= 10 , name = oomusou

印出在function內目前的值為何,因為剛改成10與oomusou,所以印出10與oomusou,符合預期。

33行

 
  
printf( " after function\n " );
printf(
" no=%d, name=%s\n " , boy.no, boy.name);
printf(
" no=%d, name=%s\n " , pboy -> no, pboy -> name);

結果為

 
  
after function
no
= 10 , name = oomusou
no
= 10 , name = oomusou

印出執行完function後的結果,由於我們是直接將pointer傳進去,所以function內所改的值是pointer所指的值,也就是實際改了原本struct的值,所以無論是印出原本struct的值:boy.no與boy.name,或者回傳struct pointer版本的pboy->no與pboy->name,其結果都是一樣的,也符合預期。

這裡要暫時岔開話題談談typedef與struct的應用,在看C爸爸的The C Programming Language [1]或者C Primer Plus [2]這兩本C的聖經時,常可看到直接使用struct而不使用typedef的範例,若以上程式改用這種寫法,將變成如下:

struct_pass_by_address_2.c / C

 
  
1 /*
2 (C) OOMusou 2011 http://oomusou.cnblogs.com
3
4 Filename : struct_call_by_address_2.c
5 Compiler : Visual C++ 10.0
6 Description : struct call by address in C
7 Release : 02/18/2011 1.0
8   */
9
10 #include < stdio.h >
11 #include < string .h > // strcpy()
12  
13   struct student {
14 int no;
15 char name[ 10 ];
16 };
17
18   struct student * struct_call_by_address( struct student * pboy) {
19 pboy -> no = 10 ;
20 strcpy(pboy -> name, " oomusou " );
21 printf( " in function:\n " );
22 printf( " no=%d, name=%s\n " , pboy -> no, pboy -> name);
23 return pboy;
24 }
25
26   int main() {
27 struct student boy = { 20 , " John " };
28 struct student * pboy = 0 ;
29
30 printf( " before function\n " );
31 printf( " no=%d, name=%s\n " , boy.no, boy.name);
32 pboy = struct_call_by_address( & boy);
33 printf( " after function\n " );
34 printf( " no=%d, name=%s\n " , boy.no, boy.name);
35 printf( " no=%d, name=%s\n " , pboy -> no, pboy -> name);
36 }

13行

 
  
struct student {
int no;
char name[ 10 ];
};

直接宣告student這個struct型別,沒有使用typedef。

27行

 
  
struct student boy = { 20 , " John " };
struct student * pboy = 0 ;

導致要宣告struct變數與pointer時,都要加上struct這個keyword。

 
  
struct student * struct_call_by_address( struct student * pboy) {

在宣告function時也一樣,必須加上struct這個keyword。

以我trace code的經驗,實務上我是沒看過人這樣寫,因為隨時要加上struct修飾的寫法不太像C語言的風格,幸好C語言還有typedef,透過typedef來定義struct型別,還可順便連pointer版本一起定義,之後的宣告都會比較方便。

接下來回到本文的主題:struct call by value

struct_call_by_value.c / C

 
  
1 /*
2 (C) OOMusou 2011 http://oomusou.cnblogs.com
3
4 Filename : struct_call_by_value.c
5 Compiler : Visual C++ 10.0
6 Description : struct call by value in C
7 Release : 02/18/2011 1.0
8   */
9
10 #include < stdio.h >
11 #include < string .h > // strcpy()
12  
13 typedef struct {
14 int no;
15 char name[ 10 ];
16 } student, * pstudent;
17
18 student struct_call_by_value(student boy) {
19 boy.no = 10 ;
20 strcpy(boy.name, " oomusou " );
21 printf( " in function:\n " );
22 printf( " no=%d, name=%s\n " , boy.no, boy.name);
23 return boy;
24 }
25
26   int main() {
27 student boy = { 20 , " John " };
28 student boy2;
29
30 printf( " before function\n " );
31 printf( " no=%d, name=%s\n " , boy.no, boy.name);
32 boy2 = struct_call_by_value(boy);
33 printf( " after function\n " );
34 printf( " no=%d, name=%s\n " , boy.no, boy.name);
35 printf( " no=%d, name=%s\n " , boy2.no, boy2.name);
36 }

執行結果

 
  
before function
no
= 20 , name = John
in function:
no
= 10 , name = oomusou
after function
no
= 20 , name = John
no
= 10 , name = oomusou

18行

 
  
student struct_call_by_value(student boy) {

以call by value的方式將整個struct傳進去,也將整個struct傳回來。

34行

 
  
printf( " no=%d, name=%s\n " , boy.no, boy.name);
printf(
" no=%d, name=%s\n " , boy2.no, boy2.name);

結果為

 
  
no = 20 , name = John
no
= 10 , name = oomusou

其他的部份程式碼與結果都和call by address一樣,就不在解釋。

最重要的是這兩行。

boy.no與boy.name都與before function一樣,所以原本的struct未受function影響,符合call by value的觀念。

boy2.no與boy2.name與in function一樣,顯然boy2接收了function改變之後所return出來的值,這是一個重新copy出來新的struct。

簡單來說,在call by address中,boy與pboy兩個都是同一個struct,而call by value中,boy與boy2是兩個不同的struct。

回到一開始的兩個不確定的疑問:

 
  
1 .根據以往的經驗,struct都是以call by address的方式,所以不確定C compiler是否能接受struct call by value的寫法。(須實驗)

2 .就算C compiler能接受語法,是真的call by value?還是只是call by address的syntax sugar? (須實驗)

以目前Visual C++ 10.0來看,是能接受struct call by value的方式,而且function內更改的值也不會影響到原來的struct,所以compiler能接受struct call by value,且也不是call by address的syntax sugar。

不過在蔡明志譯的C Primer Plus 5/e中文精華增訂版[2]的p.634

 
  
在舊版C的某些實作,結構並無法傳遞參數給函數,但使用指向結構的指標卻可以

為了這句話,我特別找了Turbo C 2.0作測試,這是1989年在DOS上很有名的C Compiler,結果struct pass by value也順利通過,20幾年前的C compiler都能通過了,所以其他compiler應該也沒問題,不過那些在嵌入式平台或者8051的C compiler我就不確定了。

本來到此,整個討論就該結束了,但既然實驗到struct,就讓我想起array。

array是否能pass by value呢?

array_pass_by_value.c / C

 
  
1 /*
2 (C) OOMusou 2011 http://oomusou.cnblogs.com
3
4 Filename : array_call_by_value.c
5 Compiler : Visual C++ 10.0
6 Description : array call by value in C
7 Release : 02/18/2011 1.0
8   */
9
10 #include < stdio.h >
11
12   void array_call_by_value ( int arr[]) {
13 arr[ 0 ] = 2 ;
14 printf( " in function\n " );
15 printf( " %d %d %d\n " , arr[ 0 ], arr[ 1 ], arr[ 2 ]);
16 }
17
18
19   int main() {
20 int arr[] = { 1 , 2 , 3 };
21
22 printf( " before function\n " );
23 printf( " %d %d %d\n " , arr[ 0 ], arr[ 1 ], arr[ 2 ]);
24 array_call_by_value(arr);
25 printf( " after function\n " );
26 printf( " %d %d %d\n " , arr[ 0 ], arr[ 1 ], arr[ 2 ]);
27 }

執行結果

 
  
before function
1 2 3
in function
2 2 3
after function
2 2 3

12行

 
  
void array_call_by_value ( int arr[]) {

在語法看來,好像是array以pass by value傳進去,但執行結果卻相當詭異,in function是 2 2 3,after function也是 2 2 3,這告訴我們什麼?根本就是pass by address的syntax sugar,所以array是沒有pass by value的!!

或許你會問,那function可以return arry by value嗎?

 
  
int [] array_call_by_value ( int arr[]) {
arr[
0 ] = 2 ;
printf(
" in function\n " );
printf(
" %d %d %d\n " , arr[ 0 ], arr[ 1 ], arr[ 2 ]);
}

很抱歉,這種寫法還沒執行已經是語法錯誤,C在這裡連syntax sugar也沒有,直接語法錯誤。

完整程式碼下載
struct_call_by_address.7z (標準struct以call by address傳給function,且使用typedef)
struct_call_by_address_2.7z (標準struct以call by address傳給function,不使用typedef)
struct_call_by_value.7z (struct以call by value傳給function)
array_call_by_value.7z (array以call by value傳給function,事實上是call by address的syntax sugar)

Conclusion
在本次的實驗,我們得到以下4個結論:

1.正統struct是以call by address的方式傳進function,速度較快,而且所有C compiler都可接受。

2.使用typedef一次定義無pointer與有pointer兩種型別,之後的宣告會較方便。

3.struct pass by value在絕大部分的C compiler都能通過,而且也不是pass by address的syntax sugar,不過在一些很老舊的C compiler很可能不支援。

4.array沒有pass by value,就算有也是pass by address的syntax sugar,而且根本連語法都不支援return array by value。

Reference
[1] Brian W. Kernighan, DEnnis M.Ritchie 1988, The C Programming Language, Pewnrixw Hall PTR
[2] 蔡明志 譯 2006, C Primer Plus 5/e 中文精華增訂版,碁峰資訊股份有限公司
[3] 蔡明志 2009, 指標的藝術, 碁峰資訊股份有限公司

全文完。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值