2015年4月24日 星期五

[C Language]關於指標、const、__in 、__out、IN、OUT

首先,*是指標,沒問題,但指標拿來當function argument(函式引數)時就會混淆了,好比說這個prototype:

int func(int *p);

請問它是:
1.將 *p 的值輸入給函式使用
2.還是把函式的運算結果放到 *p 中

看不出來! 有點經驗的人都知道程式是給未來的自己與其它人看的,增加可讀性的第一步是代碼,其次是註解,再來是文件;更有經驗的人會知道可讀性幾乎就等於開發效率。

基本上還有一個問題,那就是p可能是a pointer to int "array",而不是a pointer to int。

好,根據眾書本的說法,第一步是要好好取名字,例如:
int PrintAge(int *p);    //可以看出 *p 是用來輸入age,然後輸出(print)到console window
int GetAge(int *p);      //可以看出 *p "可能"是輸出age,因為也有可能從函式回傳值輸出,但已經明顯多了
int PrintAllAge(int *p); //由函式名可以推測 p 可能是a pointer to int array,因為看來會印出多個age

如果這個例子也對引數p好好命名,那會清晰更多;但內部要做什麼,仍然是毫無限制的,所以這時候const就很好用了。基本上,const有兩個功能:

1.在"宣告"變數時,將變數宣告成read-only的data
2.在修飾function argument型別時,限制function對此argument的權限為read-only
(不一定真的要傳read-only data進來,只是限制而已)

所以現在新的prototype就變成這樣了:
int PrintAge(const int *age);
int GetAge(int *result_age);
int PrintAllAge(const int *age_array);

明顯看出第1、3個函式引數是用來輸入的,第2個函式是用來輸出的。輸入/輸出是這麼重要,所以有不少的解法方案,好比說在名稱前面加上 in (e.g.: in_age),或是C++ complier內部用的empty define: __in __out [1][4][5]。

__in : 代表這個參數是input
__out : 代表這個參數是output
__in_opt : 代表這個參數是input,且可以選擇性(optional)輸入,通常選擇不輸入就是帶NULL pointer
__out_opt : 代表這個參數是output,且可以選擇性(optional)輸入,通常選擇不輸入就是帶NULL pointer

例子:
int GetAge(__out int *result_age);

由於 _ (underscore)開頭的東西,我們不要拿來用/也不要這麼命名[2],所以我們可以定義自己的用法,...或再看其它人是怎麼定義的,這時我發現UEFI的玩法還不錯,他們有定出這三個東西: IN OUT OPTIONAL,除了OPTIONAL太長,且放在參數的尾巴外,其它倒是可以學來在自己的程式上用,如:

#ifndef IN
#define IN
#endif
int PrintAge(IN  const int *age);

接下來看看一個較完整的範例:
int
SortIntArray (
IN OUT  int *array,
IN      const int array_length,
OUT OPT int *max_value_in_array,
OUT OPT int *min_value_in_array
)

OPT 就是 OPTIONAL 的意思。


最後再來個練習:
int func(int ***p);
請問這個函式是要對三維資料(注1)做操作,還是二維資料?

int func1(IN  int ***p);
int func2(OUT int ***p);
那這兩個呢?


答案是:
func1"很可能"是傳三維資料進去,func2"很可能"是傳二維資料出來,有IN OUT差很多,但!!這些還是會有問題,上面只是假設 *** 是多維的資料,但實際上有可能不是多維,就只是個 a pointer to a pointer to a pointer,雖然少見,但卻有它的用處,這就是虛擬記憶體、或記憶體管理等高等資料結構的設計。...其實怎樣都會有問題,所以就加上函式註解吧。

/*
    ***p is a "input" 3-dimentional data
*/
int func1(IN  int ***p);

/*
    ***p is a output pointer to a 2-dimentional data
*/
int func2(OUT int ***p);


最後,關於指標我非常建議去看參考資料[3]這篇文章,裡面精闢地闡述了:
1.Pointer: *
2.Constant: const
3.Reference: & (這裡是指C++的reference,而不是C的取址運算元,C++把&的用法擴充了)
4.Array: []
5.Function pointer: (*)()
對於複雜指標的用法,裡面也提供了可讀性最高的方案: 多用typedef。


結論:
1.命名,還是命名
2.函式引數能用const就用const,這是唯一能規範實作的方法
3.用 IN OUT OPT 來做進一步的提示
4.對多維資料/指標特地加上注解,至少要標出幾維


注1:
精確點來講,***是三維資料,不是三維陣列,陣列是指所有的資料都是"連續"的,而***概念類似陣列,但資料是不連續的(陣列的維合),好處是彈性,但接受這筆資料的人要知道內部各維資料的陣列長度。





參考資料
====================================================================
[1]stackoverflow: Meaning of __in , __out, __in_opt
http://stackoverflow.com/questions/9834550/meaning-of-in-out-in-opt

[2]PTT看板C_and_CPP: Re: [問題] 絕不使用單一底線開頭的名稱
https://www.ptt.cc/bbs/C_and_CPP/M.1320472279.A.C06.html

[3]Codeproject: How to interpret complex C/C++ declarations
http://www.codeproject.com/Articles/7042/How-to-interpret-complex-C-C-declarations

[4]stackoverflow: Empty define in C
http://stackoverflow.com/questions/13892191/empty-define-in-c

[5]stackoverflow: C++: Empty function macros
http://stackoverflow.com/questions/9187628/c-empty-function-macros

沒有留言:

張貼留言