第 6 章 C 言語入門
6.16 配列とポインタ(その3・多次元配列)
6.16.3 ポインタの配列と二重ポインタ
int a0[5], a1[5] ; int *b[2] = {a0, a1} ; int **c = b ;
とすれば,cには配列bの先頭のアドレスが格納される. したがって,c+1はint型のポインタの配列(ポ インタ)の意味で,インクリメントが行われるので, *(c+i)は b[i]と等価なアクセスを実現する. すな わち,この定義では,
• *(*(c+i)+j),*(c[i]+j),c[i][j]は同じ参照を表し,
• *(c+i)+j,c[i]+j,&c[i][j]は同じアドレスを表す.
6.16.3.3 配列へのポインタ
ポインタの配列と混乱をおこし易いものに,「配列へのポインタ」がある.
int (*b)[3] ;
と定義すると,bは先に間接演算子*と結合するので,「ポインタ」となり,「その指し示す先がint [3]
」と読むことができる. よって,int (*b)[3] は 「int型の3個の要素からなる配列へのポインタ」とな る. この時,
int a[3] = {1,2,3} ; int (*b)[3] ;
b = &a ;
とすることにより,(*b)[i]または(*b)+iは a[i]と等価なアクセスを実現する.
前に関数の戻り値の型として配列を返すことは出来ないと書いたが,配列へのポインタを利用すること により,類似のことを行うことは可能である.
Example 6.16.2 int型の3個の要素からなる配列へのポインタを返す関数.
#include <stdio.h>
int (*foo(int n))[3]
{
static int b[3] ; int i ;
for(i=0;i<3;i++) b[i] = n+i ; return &b ;
}
int main(int argc, char **argv) {
int (*a)[3] ; int i ; a = foo(1) ; for(i=0;i<3;i++)
printf("%p\n", (*a)[i]) ; return 0 ;
}
この関数fooでは,実際に値を代入する配列 bはstatic 宣言されている. これは, 戻り値の値(ポイン タ)が指し示す先を, 関数終了後も保持するためである.
6.16.4 配列を仮引数とする関数
関数の引数(仮引数)が単純な型やそれに対するポインタの場合は,仮引数の記述の方法は容易である が, 多重配列,構造体の配列など複雑な場合は, 仮引数の記述の方法は注意が必要である.
仮引数においては
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp
int *a int a[]
は同じであると書いた. これらは,ともにそれぞれの変数の先頭アドレスが関数実引数となる. また多重配列においても,
int **a int *a[]
それぞれで,先頭アドレスが関数引数となることは同じである. しかし, int a[][]
とした場合には,関数内でa[i][j]としてオブジェクトを指定しようとしても,a[i]としてどこを示して いるかが分からない. 具体的には,
int a[2][3] = {{1,2,3},{3,4,5}} ;
で定義された変数を関数に渡す際に, 仮引数をa[][] と書き,関数内でa[1]としたとする. この時,我々 が期待するのは,a[1]が{3,4,5}という配列であるが,実際には,aのアドレス(すなわち,a[0]を示す アドレス)に対して,a[1]を示すアドレスとの差が分からないので,a[1] を正しく参照することができな い. これを回避するためには, 仮引数として a[][3]と宣言する必要がある. こうすれば, a[0] に対して a[1]が int型の3つ分のずれがあることが分かる.
一方,仮引数として*a[2] とした関数に対して,二重配列 a[2][3]を実引数とすると,すなわち, void foo(int *a[2])
{
return ; }
int main(int argc, char **argv) {
int a[2][4] ; foo(a) ; return 0 ; }
とすると,
warning: passing arg 1 of ‘foo’ from incompatible pointer type
という警告が出される. これは,仮引数int *a[2]と実引数int a[2][4]は,そのオブジェクトの型が異 なることが理由である. なぜなら,関数内でa[i][j]を参照しようとすると,先頭アドレスaからポインタ のバイト数の i倍先のアドレスが参照され, そこに書かれているアドレスを参照して,そこからint型の バイト数のj倍先のアドレスにある値が参照される. しかし,実引数として渡されたオブジェクトは,int 型のオブジェクトの2×4個のならびであるので,実引数側ではa[i][j]を参照するには,aからint型 のオブジェクトのバイト数の4×i+j倍先を参照しなければならない.
*a[2]で期待されているデータ構造
a[0]のアドレス,例えば0xeeff a[1]のアドレス,例えば0xef20
✯
❥
0xeeff番地
0xef20番地 a[2][4]のデータ構造
0x0001
✲????
アドレスが32ビット(=4バイト),int型が4バイトの時,int *a[2]と仮引数が宣 言された関数内でa[0][0]を参照すると,a[0][0]の値をアドレスと思い,そのアドレ スを参照する.
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp
これを正しく動作させるためには, void foo(int a[][4])
{
return ; }
int main(int argc, char **argv) {
int a[2][4] ; foo(a) ; return 0 ; }
とする必要がある. しかし,仮引数として**aとした関数に対して,*a[2]と定義したオブジェクトを実引 数とすることは可能である.
void foo(int **a) {
return ; }
int main(int argc, char **argv) {
int *a[2] ; foo(a) ; return 0 ; }
また,この関数fooは void foo(int *a[])
として定義しても動作は変らない.
よって,多重配列を関数に渡そうとするとき,関数仮引数定義では最も左の添字は省略が可能であること がわかる. したがって,
int bar(int a[][5][4]) と定義した関数に
int a[5][5][4], b[100][5][4] ; を実引数とすることが可能であるが,
int c[5][4][4], d[5][5][10], e[2][10][15] ; 等を実引数とすることは出来ない.
これら多重配列を仮引数とする関数の関数プロトタイプ宣言は int foo(int [][5][4])
などと,識別子を省略することができる.
以上をまとめると,「関数仮引数での配列はポインタ」であると考えれば良いことがわかる. 多重配列で は, 逆に「関数仮引数でのポインタを配列にする」ことはできない.
Example 6.16.3 次は二重配列の各種の扱いである.
#include <stdio.h>
int print_matrix_1(int [][], unsigned int) ;
int print_matrix_2(int *[], unsigned int, unsigned int) ; int print_matrix_3(int **, unsigned int, unsigned int) ; int main()
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp
{
int a[3][4] = {{1,2,3,4},{2,3,4,5},{3,4,5,6}} ; int *b[3] = {a[0], a[1], a[2]} ;
int **c = b ; print_matrix_1(a,3) ; print_matrix_2(b,3,4) ; print_matrix_3(c,3,4) ; return 0 ;
}
int print_matrix_1(int a[][4], unsigned int n) {
unsigned int i, j ; for(i=0;i<n;i++) {
for(j=0;j<4;j++) {
/* printf(" %d",a[i][j]) ; */
/* printf(" %d",*(a[i]+j)) ; */
printf(" %d",*(*(a+i)+j)) ; }
printf("\n") ; }
printf("\n") ; return 0 ; }
int print_matrix_2(int *a[], unsigned int n, unsigned int m) {
unsigned int i, j ; for(i=0;i<n;i++) {
for(j=0;j<m;j++) {
/* printf(" %d",a[i][j]) ; */
/* printf(" %d",*(a[i]+j)) ; */
printf(" %d",*(*(a+i)+j)) ; }
printf("\n") ; }
printf("\n") ; return 0 ; }
int print_matrix_3(int **a, unsigned int n, unsigned int m) {
unsigned int i, j ; for(i=0;i<n;i++) {
for(j=0;j<m;j++) {
/* printf(" %d",a[i][j]) ; */
/* printf(" %d",*(a[i]+j)) ; */
printf(" %d",*(*(a+i)+j)) ; }
printf("\n") ; }
printf("\n") ; return 0 ; }
Example 6.16.4 上の例と間違えやすいものの例.
#include <stdio.h>
extern int print_p(int (*)[], unsigned int) ; int main()
{
int a[3] = {1,2,3} ;
Id: C7-2.tex,v 1.2 2001-03-21 19:38:00+09 naito Exp
int (*b)[3] ; b = &a ; print_p(b,3) ; return 0 ; }
int print_p(int (*b)[], unsigned int n) {
unsigned int i ;
for(i=0;i<n;i++) printf(" %d", (*b)[i]) ; printf("\n") ;
return 0 ; }