Page List

Search on the blog

2010年9月21日火曜日

配列を関数に渡す

C/C++を使用する人なら、値渡しアドレス渡しがあるのは知っていると思います。
しかし、配列は値渡しをしたつもりでもアドレス渡しになってしまうようです。
また、配列を関数に渡す場合には、どうすればいいか迷うことも多いでしょう。特に多次元配列の場合はそうです。

まず、配列が他の型とは違う挙動をすることを実際にアドレスを表示させることで確認してみましょう。

  1. /* 
  2. * int address 
  3. */  
  4. void func1(int n) {  
  5.    printf("The address of input is %d.\n\n", &n);  
  6. }  
  7.   
  8. /* 
  9. * buffer address 
  10. */  
  11. void func2(int n[]) {  
  12.    printf("The address of input is %d.\n\n", n);  
  13. }  
  14.   
  15. /* 
  16. * STL address 
  17. */  
  18. void func3(vector<int> n) {  
  19.    printf("The address of input is %d.\n\n", &n);  
  20. }  
  21.   
  22. /* 
  23. * class address 
  24. */  
  25. class Human{  
  26. public:  
  27.    int age;  
  28.    string name;  
  29. };  
  30. void func4(Human n) {  
  31.    printf("The address of input is %d.\n\n", &n);  
  32. }  
  33.   
  34. int main() {  
  35.    int a;  
  36.    printf("The address of input is %d.\n", &a);  
  37.    func1(a);  
  38.   
  39.    int b[10];  
  40.    printf("The address of input is %d.\n", b);  
  41.    func2(b);  
  42.   
  43.    vector<int> c;  
  44.    printf("The address of input is %d.\n", &c);  
  45.    func3(c);  
  46.   
  47.    Human d;  
  48.    printf("The address of input is %d.\n", &d);  
  49.    func4(d);  
  50.   
  51.   
  52.    return 0;  
  53. }  


配列の場合のみ、呼び出し元と呼び出し先で同じアドレスが出力されます。つまり、配列は値渡ししたつもりでも実際はアドレス渡しになるということです。もちろん関数のプロトタイプ宣言部のint n[]をint *nと書くことも出来ます。こちらの方がアドレスを渡していると明示的に分かるので良いかもしれません。

では、次の問題。多次元配列を関数に渡す時はどうするか。
多次元配列は実際は一次元方向にメモリーが展開されるということを知っておくことが大切です。実際に見てみましょう。

  1. int main() {  
  2.   int x[3][3][3];  
  3.   
  4.   REP(i, 3)  
  5.       REP(j, 3)  
  6.           REP(k, 3)  
  7.               printf("%d\n", &x[i][j][k]);  
  8.   
  9.   return 0;  
  10. }  

どうでしょうか?
メモリが32bit intの環境では4byteずつ増えていると思います。
つまりメモリ上は多次元配列は一次元配列と同じ形をしているということです。
多次元配列を宣言した関数内では、それぞれが何次元で構成されているか分かりますが、関数に渡してしまうと呼び出しさきでは、何次元目がどれくらいの単位で区切られているか分かりません。
よって、関数に渡す際には、呼び出し先の関数のプロトタイプに配列のサイズを明記しなければなりません。
こんな感じ。

  1. void func5(int n[][10]) {  
  2.   REP(i, 10)  
  3.       REP(j, 10)  
  4.           printf("%d%c", n[i][j], (j==9) ? '\n' : ' ');  
  5.   
  6.   return;  
  7. }  
  8.   
  9. int main() {  
  10.   int buf[10][10];  
  11.   memset(buf, 0, sizeof(buf));  
  12.   func5(buf);  
  13.   
  14.   return 0;  
  15. }  


今日の話は基本中の基本って感じですが、やっぱり基本はしっかり押さえておきたいものです。

0 件のコメント:

コメントを投稿