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嗎?
在實驗之前,我們先用既有的認知做推理:
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
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 }
執行結果
no = 20 , name = John
in function:
no = 10 , name = oomusou
after function
no = 10 , name = oomusou
no = 10 , name = oomusou
13行
int no;
char name[ 10 ];
} student, * pstudent;
使用typedef重新定義struct型別student,並且順便定義pointer版本的pstudent,因為struct傳進function時一定用的到pointer,實務上都是兩個型別一起宣告在header file(*.h)中。
27行
宣告boy變數,並且一起做初始化。
28行
宣告pboy這個指向struct的pointer,並且馬上指定為0(或者NULL)避免wild pointer的發生,這是一個好習慣。
18行
定義struct_call_by_address() function,注意傳進去的是pointer版本的struct,回傳的也是pointer版本的struct。
30行
printf( " no=%d, name=%s\n " , boy.no, boy.name);
結果為
no = 20 , name = John
先印出執行function前的值,結果為27行剛出始化的27與John,符合預期。
32行
呼叫struct_pass_by_address() function,由於要傳進去的是pointer,所以必須&boy;因為傳回的也是pointer,所以使用pboy。
19行
strcpy(pboy -> name, " oomusou " );
實際去改變struct值,因為傳進function的為pointer pboy,所以要用->,這是C的語法規定;另外C的字串也規定要用strcyp(),無法用pboy->name = "oomusou"這種直覺的語法(C++可以),既然在這裡用C,就只好認了。
21行
printf( " no=%d, name=%s\n " , pboy -> no, pboy -> name);
結果為
no = 10 , name = oomusou
印出在function內目前的值為何,因為剛改成10與oomusou,所以印出10與oomusou,符合預期。
33行
printf( " no=%d, name=%s\n " , boy.no, boy.name);
printf( " no=%d, name=%s\n " , pboy -> no, pboy -> name);
結果為
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
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行
int no;
char name[ 10 ];
};
直接宣告student這個struct型別,沒有使用typedef。
27行
struct student * pboy = 0 ;
導致要宣告struct變數與pointer時,都要加上struct這個keyword。
在宣告function時也一樣,必須加上struct這個keyword。
以我trace code的經驗,實務上我是沒看過人這樣寫,因為隨時要加上struct修飾的寫法不太像C語言的風格,幸好C語言還有typedef,透過typedef來定義struct型別,還可順便連pointer版本一起定義,之後的宣告都會比較方便。
接下來回到本文的主題:struct call by value
struct_call_by_value.c / C
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 }
執行結果
no = 20 , name = John
in function:
no = 10 , name = oomusou
after function
no = 20 , name = John
no = 10 , name = oomusou
18行
以call by value的方式將整個struct傳進去,也將整個struct傳回來。
34行
printf( " no=%d, name=%s\n " , boy2.no, boy2.name);
結果為
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。
回到一開始的兩個不確定的疑問:
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
為了這句話,我特別找了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
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 }
執行結果
1 2 3
in function
2 2 3
after function
2 2 3
12行
在語法看來,好像是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嗎?
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, 指標的藝術, 碁峰資訊股份有限公司
全文完。