計数工学プログラミング演習 ( 第 3 回 ) 2016/04/26 DEPARTMENT OF MATHEMATICAL INFORMATICS 1
内容 ポインタ malloc 構造体 2
ポインタ あるメモリ領域 ( アドレス ) を代入できる変数 型は一致している必要がある 定義時には値は不定 ( 何も指していない ) 実際にはどこかのメモリを指しているので, #include<stdio.h> そこに書き込むと実行時にエラー int main() double B[3] = 1.0, 2.0, 3.0; double *p; // 実数型のポインタ p = B; // p に B のアドレスを代入 printf( p[1] = %lf n, p[1]); p[1] = 4.0; // 実際には B[1] に代入される printf( B[1] = %lf n, B[1]); return 0; 3
メモリ確保 ポインタは宣言しただけではメモリは確保されず, 初期値は不定 不定なアドレスを読み書きすると通常はsegmentation faultで終了 たまたま動く場合も, 他の変数の値を破壊することがある malloc 関数で必要なサイズ ( 以上 ) の領域を確保する必要がある void main(void) yをポインタとして宣言 int *y; メモリのどこに書かれる *y = 1; か分からない printf("main: y = %d n", *y); 間違い void main(void) int *y; y = malloc(sizeof(int)); *y = 1; free(y); 正解 整数 1 つ分の領域を確保 4
エラーチェック malloc に必要なメモリが無い場合はエラー 返り値が NULL かどうかチェックする y = malloc(sizeof(int)); if (y == NULL) printf( Not enough memory. n ); exit(1); // プログラム終了 *y = 1; 5
関数呼び出し #include <stdio.h> #include <stdlib.h> void sub_value(int x) x = x + 1; printf("sub_value: x = %d n", x); void main(void) int x; x = 1; printf("main: x = %d n", x); sub_value(x); printf("main: x = %d n", x); 実行結果 main: x = 1 sub_value: x = 2 main: x = 1 6
引数がポインタの場合 x は整数変数へのポインタ void sub_reference(int *x) *x = *x + 1; ポインタの中身を参照 printf("sub_reference: x = %d n", *x); void main(void) int y; y = 1; printf("main: y = %d n", y); yのアドレス sub_reference(&y); printf("main: y = %d n", y); 実行結果 main: y = 1 sub_reference: x = 2 main: y = 2 7
引数が配列の場合 配列は参照渡しになる void sub_reference(int x[]) x[0] = x[0] + 1; printf("sub_reference: x = %d n", x[0]); void main(void) int y[1]; y[0] = 1; printf("main: y = %d n", y[0]); sub_reference(y); printf("main: y = %d n", y[0]); 実は int *x と書いても同じ 実行結果 main: y = 1 sub_reference: x = 2 main: y = 2 8
長さ可変の配列 ポインタ int A[n]; などは ( 基本的には ) できないので, 動的に確保する #include<stdio.h> 実数 1 個が何バイトかを返す. 通常は8バイト. int main() double *A; int i, n; scanf( %d, &n); A = malloc(n * sizeof(double)); // 実数 n 個分の領域を確保してそのアドレスを A に代入 for (i=0; i<n; i++) A[i] = (double)i; free(a); // 使わなくなった領域を開放する return 0; 不要になった領域は必ず free を実行して回収する. 回収しないと, プログラムが使えるメモリがだんだん減っていく ( メモリリーク ) free したあとの領域をアクセスしてはいけない 9
ポインタの演算 整数へのポインタを 1 増やすと, アドレスが整数 1 個分増える 配列の要素は連続したアドレスに格納されるので, ポインタを 1 ずつ増やすと配列の要素に順番にアクセスできる 配列の長さを超えてもポインタは増やせるのでバグに注意 コンパイルはできるが実行するとエラー int A[10]; for (i=0; i<10; i++) A[i] = 0; int *p, A[10]; p = A; for (i=0; i<10; i++) *p = 0; p += 1; 10 *p++ = 0; とも書ける
変数を格納するメモリ領域 /* test4.c */ #include<stdio.h> #include<stdlib.h> int a[10]; // グローバル変数 ( どの関数からもアクセス可 ) int main() int b[10]; // ローカル変数 ( この関数内からのみアクセス可 ) int *c; // 動的に確保する領域へのポインタ int i; printf("address of a = %p, size of a[i] = %d bytes n", a, (int)sizeof(a[0])); for (i=0; i<10; i++) printf("address of a[%d] = %p n", i, &a[i]); printf("address of b = %p, size of b[i] = %d bytes n", b, (int)sizeof(b[0])); for (i=0; i<10; i++) printf("address of b[%d] = %p n", i, &b[i]); c = (int *)malloc(10*sizeof(*c)); // ヒープにメモリを確保 printf("address of c = %p, size of c[i] = %d bytes n", c, (int)sizeof(c[0])); for (i=0; i<10; i++) printf("address of c[%d] = %p n", i, &c[i]); printf("address of &a = %p, sizeof(&a) = %d bytes n", &a, (int)sizeof(&a)); printf("address of &b = %p, sizeof(&b) = %d bytes n", &b, (int)sizeof(&b)); printf("address of &c = %p, sizeof(&c) = %d bytes n", &c, (int)sizeof(&c)); free(c); return 0; 11
実行例 %./a.out Address of a = 0x601060, size of a[i] = 4 bytes Address of a[0] = 0x601060 Address of a[1] = 0x601064 Address of b = 0x7fff0347a1a0, size of b[i] = 4 bytes Address of b[0] = 0x7fff0347a1a0 Address of b[1] = 0x7fff0347a1a4 Address of c = 0x143d010, size of c[i] = 4 bytes Address of c[0] = 0x143d010 Address of c[1] = 0x143d014 Address of &a = 0x601060, sizeof(&a) = 8 bytes Address of &b = 0x7fff0347a1a0, sizeof(&b) = 8 bytes Address of &c = 0x7fff0347a198, sizeof(&c) = 8 bytes 実行中に確保される領域 ( サイズ不変 ) 0x7fff0347a1a0 0x7fff0347a198 実行中に確保される領域 ( サイズ可変 ) 0x143d010 実行前に確保される領域 ( サイズ不変 ) 0x601060 b[1] b[0] &c c[1] c[0] a[1] a[0] 12
サイズ可変の 2 次元配列の実現法 n 行 m 列の配列を作る場合 各行は, 長さ m の (1 次元 ) 配列 malloc (m * sizeof(double)) で確保 2 次元配列は, 行ベクトルの配列 M[] として実現する M[i] は i 行目に対応する配列を指すポインタ 配列へのポインタの型は double * だったので,M[i] の要素の型は double * M の長さ n は実行時に決まるので, ポインタとして定義して実行時に malloc で領域を確保する つまり M の型は double ** となる (double へのポインタ ) へのポインタ 13
ポインタのポインタ #include <stdio.h> #include <stdlib.h> double **create_matrix(int n, int m) double **R; // R は (double へのポインタ ) へのポインタ int i, j; R = malloc(n*sizeof(double *)); // R は n 個のポインタを格納できる配列 for (i=0; i<n; i++) R[i] = malloc(m*sizeof(double)); // R[i] は (i+1) 行目へのポインタを格納 for (i=0; i<n; i++) for (j=0; j<m; j++) R[i][j] = 0.0; return R; R[2] R[1] R[0] R &R R[2][1] R[2][0] R[1][1] R[1][0] R[0][1] R[0][0] R[2] R[1] R[0] R 14
struct ( 構造体 )1 複数の変数をまとめたもの struct matrix int n, m; // 行数, 列数 double **A; // 行列の中身 ; 15
struct ( 構造体 )2 typedef で構造体に名前をつけると便利 typedef struct int n, m; // 行数, 列数 double **A; // 行列の中身 matrix; matrix *X, *Y; // 行列の構造体へのポインタの宣言 X = malloc(sizeof(matrix)); // 構造体を格納するメモリを確保 X-> n = 3; X->m = 2; // 行数, 列数を定義 16
struct ( 構造体 )3 注意 : ポインタの代入では中身は複製されない int main() matrix *X, *Y; X = malloc(sizeof(matrix)); X->n = 3; Y = X; // ポインタの代入 Y->n = 4; 実行結果 X->n = 4 Y->n = 4 printf( X->n = %d n", X->n); printf( Y->n = %d n", Y->n); int main() matrix *X, *Y; X = malloc(sizeof(matrix)); X->n = 3; Y = malloc(sizeof(matrix)); // 新しい領域の確保 Y->n = 4; printf( X->n = %d n", X->n); printf( Y->n = %d n", Y->n); 実行結果 X->n = 3 Y->n = 4 17
struct ( 構造体 )4 関数の返り値を構造体へのポインタにする matrix *create_matrix(int n, int m) // 新しい零行列を作る関数 int i, j; matrix *X; X = malloc(sizeof(matrix)); // 構造体を格納するメモリを確保 X-> n = n; X->m = m; // 行数, 列数を構造体に代入 X->A = malloc(n*sizeof(double *)); // 行ベクトルの配列のメモリを確保して構造体に代入 // ここで列ベクトルのメモリを確保 for (i=0; i<n; i++) for (j=0; j<m; j++) X->A[i][j] = 0.0; // 値を初期化 return X; int main() matrix *A; A = create_matrix(3, 2); 18
第 3 回課題 : 2 つの行列の積を計算するプログラム 入力行列は標準入力から与えられる 1 行目は行列の行数と列数 1 つ目の行列のデータの直後に 2 つ目の行列のデータが来る 標準入力から続けて読めばいい 結果は標準出力に 入力と同じ形式 積を計算できない場合は 0 行 0 列の行列を出力する 出力は 0 0 の 1 行だけ 行列の形式 2 3 // 行数, 列数 1.0 2.0 3.0 // 1 行目 4.0 5.0 6.0 // 2 行目 main 関数の最後は return 0; をつけておく 提出するソースコードの中に, 学生証番号, 氏名, メールアドレスをコメントとして書いておく 締切 : 2016 年 4 月 26 日 ( 火 ) 17:35 ( 演習終了時 ) http://133.11.136.29/domjudge/ 19
ヒント : 以下の関数を順番に作っていけばいい matrix *create_matrix(int n, int m) n 行 m 列の零行列のメモリを確保する matrix *read_matrix() 標準入力から行列のデータを読み, 新しく行列を作る 行列のメモリはこの関数内で確保する void print_matrix(matrix *A) 行列を指定された形式で標準出力に書く matrix *product(matrix *A, matrix *B) 行列積を求め, 新しい行列を作る void free_matrix(matrix *X) 行列のメモリを開放する ( 作らなくても動くが ) 20