プログラミング入門 (前川) 65 6 配列 6.1 配列の宣言、配列要素、配列の添字 計算機が最も得意とするものは、大量のデータの繰り返し処理である。このようなデータは配列 (array) で扱うのが便利だ。 たとえば、int(整数) 型のデータ 1000 個を用意し、その値をすべてゼロにクリアするプログラムは次のよう になる。 リスト 6.1 int 型配列により 1000 個の整数型のメモリセルを確保し, クリアする. int a[1000]; int i; for(i=0; i<1000; i++) a[i] = 0; このコードの冒頭にある宣言 int a[1000] によって、整数型のメモリセルが 1000 個連続した配列 a が 用意される。配列につけた名前 (ここでは a) を配列名という。配列の各要素を配列要素といい、先頭の要素を a[0]、2 番目を a[1]、· · ·、i 番目を a[i-1] のように表す。配列要素を示すために使われる 0、1、· · ·、i-1、 · · · を配列の添字 (インデックス) という。 上のコードでは、int 型変数 i を配列の添字とし、それを 0 から 999 まで変化させて 1000 個のメモリ・セ ルに 0 を代入している訳だ。つまり、これらの配列要素は、通常の変数と全く同様に扱うことができる。この イメージを図 6.1 に示す。 a[0] a a[1] 0 a[2] 0 a[3] 0 a[4] 0 a[5] 0 0 ··· a[997] a[998] a[999] 0 0 0 図 6.1 宣言文「int a[1000];」で配列 a を確保し, 全ての要素を値 0 でクリアした状況. 変数名はプログラマが比較的自由につけることができるので、同じ種類の多数の変数が必要なとき変数名 を a0, a1, a2 ... と変えていくこともできる。しかし、これらの変数に一定の処理を繰り返し施す場合を考 えると、そのコードは、同じ操作で変数名だけが違うものが沢山並んだ冗長なものになることは容易に想像で きる。いろいろな場面で「配列」を使いこなせるよう、文法事項と実際のプログラミングでしっかり学んでお こう。 これまでに何度か述べてきたように、計算機の内部では数値であれ文字であれ、すべて 0, 1 の並び (ビット パターン) で表現されている。画像データの場合は、画面上の各点 (画素) の明るさを数値化して表現する。モ ノクロームの濃淡画像であれば、最も暗い値から最も明るい値を 0 から 255 の値で表現し、カラー画像の場合 は、光の三原色 (Red, Green, Blue) のそれぞれをこの範囲で表現する。 簡単な例として、モノクロ濃淡画像データを考える。0 から 255 の範囲は 1 バイトで表現できるので、 100 × 100 = 10000 画素の領域を「真っ白」に塗り潰すには、次のようにする。とは言っても、このプログラ ムで画面が真っ白になるという訳でなく、「画像に見立てたメモリ領域を白に相当する値で埋める」というこ とである。但し、このようなデータを実際の画像として扱う例は、このすぐ後に示す。 /* 10000(100x100) の領域 (canvas) を宣言し, 白く塗り潰す */ unsigned char canvas[10000], i; for(i=0; i<10000; i++) canvas[i] = 255; プログラミング入門 (前川) 66 ここで型名 char の前に使われている使われている unsigned は、「符号なし」の意味で、char 型のサイズ (1 バイト) をすべて正の範囲に使うということを (明示的に) 指定している。このような「修飾子」は、他に signed, short, long などがある。 ところで、上の例は、画像の大きさを示す 10000 や、明るさを示す 255 といった数値が生のまま書かれて いる。これでは、画像サイズを変更する場合や塗り潰す明るさを変えたい場合に不便なので、先にみた「マク ロ置換」を使って、次のようにするのが C の常套手段だ。 リスト 6.2 10000(100x100) の領域 (canvas) を宣言し, 白く塗り潰す. #define IMAGE_SIZE #define BG_LEVEL 10000 255 unsigned char canvas[IMAGE_SIZE], i; for(i=0; i < IMAGE_SIZE; i++) canvas[i] = BG_LEVEL; 以上、いくつかの例を示したように、C の配列宣言は次のような形をしている。 リスト 6.3 C の配列宣言法.「配列のサイズ」とは, 配列要素の数. 要素位置を示す添字は, 0 から始まる事に注意する. 配列要素のデータ型 配列名 [ 配列のサイズ ] ; 6.2 画像データを作る 画像データを配列の例として示したが、これをもう少し詳しく見てみよう。 前述のように、画像データは、画面上の各点の明るさ (各画素の輝度) を数値化して表現する。モノクロー ムの濃淡画像であれば、最も暗い値から最も明るい値を 0 から 255 の値で表現し、カラー画像の場合は、光の 三原色 (Red, Green, Blue) の各輝度レベルをこの範囲で表現する。 画素の配置法 2 次元的な画素の配置法についてはいくつかの流儀があるが、ここでは、左上隅を原点 (0,0) とし、横座標 x、縦座標 y の増加方向を、それぞれ、右方向、下方向にとる。画像の横幅と高さをそれぞれ W, H とすると、 画素は次のように配置されたものとして扱う。 (0, 0) (0, 1) .. . (1, 0) (1, 1) ... .. . ... (0, H − 1) (1, H − 1) ... ... ... ... (x, y) . . . ... ... (W − 1, 0) (W − 1, 1) .. . ... .. . (W − 1, H − 1) 図 6.2 画素配置の例. この画素データは、“横書きの文章を読むように” 解釈する。つまり原点即ち左上隅のデータから開始して、 横 (水平) 方向に進み、画像の右端まで行くと一つ下の行の左端に戻り、· · · という形でセットされる。これ は、TV の画面を作る走査線の動きと同じで、ラスタ・スキャンという。 プログラミング入門 (前川) 67 画素位置とメモリの関連 上記のような 2 次元的なデータであっても、計算機のメモリ上では 1 次元的な並びとして格納される。C で は 2 次元配列も作れるが、ここではメモリイメージに近い 1 次元的な処理法を学ぼう。 上に示した画像の全画素数 L は、(画像の幅 W ) × (画像の高さ H) である。 L=W ×H (1) ラスタスキャンで各画素をメモリに格納すると、位置 (x, y) にある画素は、1 次元的なメモリ上では、先頭 から k = x + y×W (2) の位置 k に格納される。逆に、メモリ先頭から k 番目のデータは、幅 W の画像中では、2 次元的な位置 (k を画像の幅 W で割った余り, k を画像の幅 W で割った商) (3) に配置されるべき画素ということになる。これは、C の整数計算では、剰余演算子 % と除算演算子/によって 次のように書ける。 ( k % W, k / W ) (4) 従って、 「画像データを最初に塗り潰し、斜めに線を引く」プログラムの断片は次のようになる。 リスト 6.4 幅 WIDTH× 高さ HEIGHT の領域を白く塗り潰し, 斜線 y = x を引くプログラムの断片. #define WIDTH 200 #define HEIGHT 200 #define LEVEL 255 #define SIZE (WIDTH*HEIGHT) ..... unsigned char img[SIZE]; int x,y,k; for(k = 0; k < SIZE; k++) img[k] = LEVEL; for(k = 0; k < SIZE; k++){ x = k % WIDTH; y = k / WIDTH; if( y == x) img[k]=0; } ..... 実際には、このようにして作った画像をファイルとして保存したり、表示したりする必要がある。画像ファ イルには様々な書式が定められている。 ランダムな点の描画 ここでは、参考として pgm フォーマット (portable graymap file format) で 出力するプログラムを次に示 す (リスト 6.5)。但し、これは、上に示した「直線 y = x を引くプログラム」(リスト 6.4) を少し変えて、 「ラ ンダムな白黒の点」を描画するようにしている。 画像データは画素の明るさなので非負整数としてよい。このため、リスト 6.5 では符号無しの短い (1 バイ トの) 整数型として unsigned char を用いて プログラミング入門 (前川) 68 unsigned char img[SIZE]; と宣言している。 リスト 6.5 矩形領域にランダムな点を描く (pgm-P5 rand.c). // Random Dots by pgm format. pgm フォーマットでのランダムドット描画. // 乱数を発生させ,その末尾ビットが 1 なら黒,そうでなければ背景色を描画する. #include <stdio.h> #include <stdlib.h> #define WIDTH 200 #define HEIGHT 200 #define LEVEL 255 #define SIZE // 画像の幅。pgm ファイルヘッダの 3 行目は、「幅 高さ」 // 画像の高さ。 // 画素 (明るさ) の最大値。ヘッダの 4 行目に書く。 (WIDTH*HEIGHT) // 画像サイズ。pgm とは無関係。 int main(void) { unsigned char img[SIZE]; int x,y,k, mask = 1; fprintf(stdout, fprintf(stdout, fprintf(stdout, fprintf(stdout, // mask は LSB を見るためのパターン // ここから pgm ファイルのヘッダを書く。 "P5\n"); // モノクロ RAW BIT を示す "P5" "# just a try.\n"); // 2 行目は、pgm ファイルのコメント。 "%d %d\n", WIDTH, HEIGHT); "%d\n", LEVEL); for(k = 0; k < SIZE; k++){ //メモリ上で画像を作る x = k % WIDTH; y = k / WIDTH; img[k]=( (rand() & mask) ? 0 : LEVEL); //LSB が 1 なら「黒 0」を塗る // img[k]=(((rand() & mask)==1) ? 0 : LEVEL); //同上 } fwrite(img, sizeof(char), SIZE, stdout); fclose(stdout); return 0; } このプログラムは、これまでの例と異なり、標準出力に生のバイト・データを fwrite によって書き出して いる。そのため、次のように、必ず適当なファイルにリダイレクトして保存する必要がある。 % gcc -o pgm -Wall pgm-P5_rand.c % ./pgm > random.pgm 図 6.3 コンパイル法と、実行例。結果を適当な画像ファイルにリダイレクト。 上の例では、作った画像データを random.pgm というファイルに書き出している。拡張子.pgm は、このファ イルが pgm フォーマットの画像であることを示しており、画像を見るための適当なソフトウェア (イメージ・ ビューア) で見ることができる。たとえば、eog(Eye of GNOME) での確認は次のようになる*1 。 *1 画像を見るための “イメージ・ビューア”: Windows 系には IrfanView を始めとして様々なものがある. UNIX/Linux 系でも, X-Window 上で古くから用いられた xv などがある. プログラミング入門 (前川) ' 69 $ % eog random.pgm & なお, ビューア xv が使える場合は, 画像ファ イルを作らず次のようにしてもよい. % ./pgm | xv - & 図 6.4 画像の確認法と出力画面. % fprintf 及び fwrite の使い方 関 数 fprintf は 、第 一 引 数 で 出 力 先 (出 力 ス ト リ ー ム) を 指 定 す る こ と が で き る 。上 の 例 で 使 わ れ ている、fprintf(stdout, "P5\n"); は、「標準出力 (stdout) に『P5(改行)』と書く」という意味で、 printf("P5\n"); の書式変換付き出力と全く等価だ。従って、printf と同様、通常のリダイレクションで任 意のファイルに切り替えることができる。 また、ここに stderr と書くと「標準エラー出力」ということになる。前に示した例 fprintf(stderr, "Key in data; month/date note weight(kg) fat(%%)\n"); は、出力先として「標準エラー出力」(stderr) を指定していた訳だ。このため、通常のデータ出力は stdout へ、(エラー時を含む) メッセージ類の出力は stderr へ、と使い分けることができる。また、入力については、 stdin が標準入力を表し、fscanf 等で用いることができる。 なお、通常のファイルを使いたい場合には、概ね次のようにすると、カレントディレクトリのファイル random.pgm に直接出力できる (これについては、別途説明する)。 FILE *fp; fp = fopen("random.pgm", "w"); ... fprintf(fp, "P5\n"); ...(以下同様) fclose(fp); ... また、データ書き出し (書式変換無し) に用いた fwrite は、次のようになっている。 fwrite(元データ, 一個のデータサイズ, データの個数, 出力先); この第 5 引数「出力先」は、fprintf の第一引数と同じ意味であり、この例では (当然ながら) 同じく stdout(標 準出力) を指している。 濃淡 (グラデーション) の描画例 次の図 (図 6.5) は、上のプログラムを少し修正して作った pgm-P5 sin.c (リスト 6.6) による画像である。 この計算には標準ライブラリの三角関数を用いているため、そのためのインクルードファイル math.h が必要 プログラミング入門 (前川) 70 で、さらにコンパイル時に数学関数のライブラリを参照するためのオプション-lm が必要である。 図 6.5 濃淡画像 (画像の空間周波数) の描画例. pgm-P5 sin.c は数学関数 sin を含むので、 gcc -Wall -lm -o pgm-sin pgm-P5_sin.c のようにコンパイルする. この図には、横方向 (x 方向) に1周期、縦方向 (y 方向) に 3 周期の濃淡変化 (明るさの変化) があるが、そ の値はソースプログラム中の img[k] = (unsigned char) LEVEL/2.0 * ( sin(2*PI/WIDTH*(x + 3 * y)) + 1); で作っている。 また、右辺の先頭にある (unsigned char) は、double で計算した LEVEL/2.0 * ( sin · · ·) の式の結果 を、符号無し整数 (char) に型変換することを示す (前出のキャスト)。三角関数 sin の演算は double で行う が、画像データとして格納するには unsigned char に戻す必要があるので、このようキャストで明示的に型 変換している。 プログラミング入門 (前川) 71 リスト 6.6 濃淡画像の例 pgm-P5 sin.c. // Sample for image file creation by RAWBITS pgm format. // バイナリ形式の画像書式である pgm ファイルのテスト。 // コンパイル法: gcc -lm ソースファイル名 ---lm; 数学関数ライブラリをリンク #include <stdio.h> #include <stdlib.h> #include <math.h> #define WIDTH 200 #define HEIGHT 200 #define LEVEL 255 // 画像の幅。pgm ファイルヘッダの 3 行目は、「幅 高さ」 // 画像の高さ。 // 画素 (明るさ) の最大値。ヘッダの 4 行目に書く。 #define SIZE (WIDTH*HEIGHT) #define PI 3.14159265358979 // 画像サイズ。 int main(void) { unsigned char img[SIZE]; int k; double x,y; fprintf(stdout, fprintf(stdout, fprintf(stdout, fprintf(stdout, // ここから pgm ファイルのヘッダを書く "P5\n"); // モノクロ RAW BIT を示す "P5" "# the 2nd try.\n"); // 2 行目は,pgm ファイルのコメント "%d %d\n", WIDTH, HEIGHT); "%d\n", LEVEL); for(k = 0; k < SIZE; k++){ // メモリ上に画像を作るループ x = k % WIDTH; y = k / WIDTH; img[k] = (unsigned char)LEVEL/2.0 * ( sin(2*PI/WIDTH*(x + 3 * y)) + 1); } fwrite(img, sizeof(char), SIZE, stdout); fclose(stdout); return 0; } 問 6.1 下記の問いに答えなさい (実際のプログラミングにより、それらの結果を試すこと)。 (1) リスト 6.4 に示したのはまず最初の for 文で白い画面を作り、二つ目の for 文でそれに黒の斜線を引く プログラムであった。これを、(a) 図の上半分を黒く塗りつぶす、及び、(b) 図の右半分を黒く塗りつぶ すようにするには、二つ目の for 文を、それぞれどのように変えればよいか?(画素配置は 図 6.2 を参照) (2) プログラム pgm-P5 sin.c を元に縦縞グラデーションの画像ファイルを作るには、リスト 6.6 をどのよ うに変えればよいか? 変更箇所と、そのコードを示しなさい。 以上、「配列」の例として画像の作成法を見た。画像の書式やファイル出力法については、まだまだいろい ろな事柄があるが、ここではこれ以上触れず、C の基本的な話題に戻ることにする。 6.3 配列の宣言と初期化 C では、次のような書き方で、配列の宣言と同時に初期値をセットする (つまり、初期化する) ことがで きる。 プログラミング入門 (前川) 72 型名 配列名 [] = {初期値_1, 初期値_2, …, 初期値_n } ; // 初期化を含んだ配列の宣言. 即ち、配列宣言と同時に、大括弧{ }で括った初期値をセットする訳だ。このとき、配列のサイズ (要素数) は、 セットされる初期値の数に合うよう、(コンパイラによって) 自動的に計算される。これはどのような型でも可 能だが、以下に int と char の例を示す。 6.3.1 int 型配列 リスト 6.7 int 型配列の宣言と初期化法。いくつかの素数a を要素とする int 型配列。 int prime[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47}; a 素数: prime number. 要素数 (要素の数) 等と混同しないように!! このように一旦確保した配列は、初期値のまま使うだけでなく、任意の整数データを保存する変数領域として 使える。また、この方法は、float や double 等の他の数値型においても同様に使える。 6.3.2 char 型配列と文字列 char 型配列の初期化は、文字列を使って次のような簡潔な形で行うことができる。この場合、末尾 (文字列 の後ろ) に自動的にナル文字’\0’ が付けられる。即ち、宣言された配列のサイズ (配列の長さ) は、設定した 文字数よりも 1 つ多くなる。 リスト 6.8 char 型配列の宣言と初期化法 (その 1)。 char chr_array[] = "Hello, world!\n" ; この宣言文のあとでは、その配列名を使って printf("%s", chr_array); のように printf の %s 変換で その文字列を表示することができる。 また、 chr_array[7] = ’W’; のように、個々の要素に任意の文字を代入することができる。また、ナ ル文字\0 を前にずらせて文字列を短くすることもできる。但し、確保した配列の長さを超えて文字列を伸ば すことはできない。 ところで、このリスト 6.8 で示した文字列による文字配列の宣言・初期化法 (その 1) は、リスト 6.7 に示し た int 型配列の初期化法とは少し異っている。それと同じように初期化するには、次のようにすればよい。 リスト 6.9 char 型配列の宣言と初期化法 (その 2)— int 型配列のリスト 6.7 に対応した方法。 char chr array[] = { ’H’, ’e’, ’l’, ’l’, ’o’, ’,’, ’\0’ } ; 単に数値を並べればよい int 型等の初期化と違って、個々の文字をシングルクォートで括らなければならない のが少し煩わしい。また、これを文字列として使うには、末尾にはナル文字’\0’ を明示的にセットしておか ねばならない点にも注意が必要だ。 勿論、大きな文字配列をまず確保したいときは、今までのように明示的に配列サイズを指定してやればよい。 #define SIZE 60000 プログラミング入門 (前川) 73 ... char chr_array[SIZE]; 問 6.2 次のリストは、文字配列の初期設定とその一部書き換えを確認するプログラム (chr array.c) である。 修正後の文字列が"Hello, world!\n"になるよう、空白部分 (1)、(2) のコードを埋めなさい (完成版の動作 を確認すること)。 #include <stdio.h> int main(void) { char chr array[] = "hEllo, World!\n"; printf("%s", chr array); // (1) // chr array[1] = ’e’; // (2) // printf("%s", chr array); // 初期設定文字列の確認 1 文字目の修正 2 文字目の修正 8 文字目の修正 修正後の文字列の確認 return 0; } 6.3.3 配列のサイズや要素数を求める 上述の方法で確保した配列のサイズ (バイト数) は、前述の sizeof 演算子を用い、sizeof(配列名) で求め ることができる。上の例 (リスト 6.7、配列名 prime) であれば、次のようにすればよい。 printf("この配列の大きさは %d バイト.\n", sizeof(prime)); しかし、通常のプログラミングで知りたいのは、(繰り返しの上限など) 配列要素数だ。それには、配列全体 のサイズを配列要素のサイズで割ればよい。つまり、どんな配列でも、必ず先頭の要素 “配列名 [0]” は存在 するので、次のようにすれば配列要素数が得られる。*2 。 リスト 6.10 宣言した配列の要素数を得る方法. どのような配列型でも使える. 配列の要素数は sizeof(配列名)/sizeof(配列名 [0]) これは、例えば次のように使う (リスト 6.11, leapyear.c)。その中で、配列を次のように初期化している。 int year[] = {2000, 2003, 2004, 2012, 2100}; この部分にテストデータを追加しても、あるいはデータを減らしても、その要素数は、次の int yn = sizeof(year)/sizeof(year[0]); で計算しているので、for ループを含むプログラムの本体は変更することなく使うことができる (そうでない と、データ数を変更する度にプログラマがその要素数を数えなければならないことになってしまい、配列の宣 言・初期化の便利さが台無しになる)。 *2 文献 [1] では, これをマクロで書いてある. プログラミング入門 (前川) 74 リスト 6.11 配列の要素数をプログラム中で計算して使う例, 閏年の計算 (leapyear.c). #include <stdio.h> int main(void) { int year[] = {2000, 2003, 2004, 2012, 2100}; // int 型の配列 (テストデータ) int i, y, yn = sizeof(year)/sizeof(year[0]); // yn:配列 year の要素数 for(i = 0; i < yn; i++){ y = year[i]; printf("%d 年 leap=%d\n", y, // 二つ目の %d は, 下の「式の値」を表示する. ( (y % 100 == 0) && (y % 400 ==0) ) || ( (y % 100 != 0) && (y % 4 ==0) ) ); } return 0; } 問 6.3 以下の問いに答えなさい。 (1) リスト 6.11 の実行結果 (出力) はどうなるか示しなさい。 (2) (リスト 6.11 を参考に) リスト 6.7 の配列 prime の要素を全て打ち出す (表示する) プログラムを書き なさい。 6.4 配列要素の並び方–変数のアドレス これまでの説明のように、同じ型のデータが連続したものが「配列」であるが、ここで、その計算機メモリ 内の様子を詳しく見てみよう。 6.4.1 アドレス演算子 & とアドレスの表示法– %p 変換 たとえば、 int data; scanf("%d", &data); のように、変数や配列要素につけて用いる演算子&(ア ンパサンド) をアドレス演算子という。このコード例は、標準入力からのデータを int(整数) 型に変換して格 納するメモリ番地として data のアドレスを求めることを表している。 普通はその「アドレス」がどのようなメモリ番地を示すのか、意識する必要はないが、ここではそれをちょっ と “詮索” してみよう。メモリ番地 (アドレス) を printf などで表示できるように変換するのが、書式変換 子 %p だ。上のコード例では、printff("%p", &data); のように表示できる。 6.4.2 int 型配列 プログラムテスト用の整数データとしては、たとえば「オール零」など、どんなものを用いてもよいが、こ こではある範囲の乱数を生成してみよう。 前述のように、ライブラリ関数 rand() は、0 から RAND_MAX までの乱数を返す。これをある数 N で割っ て、その余り (剰余) を求めれば、それは 0∼(N − 1) の値におさまる。従って、min 以上 max 以下の乱数を求 めるには、剰余演算子 % を使って、min + rand() % (max - min + 1) とすればよい。次に示すのは、配 列要素の内容をプリントするプログラムとその実行結果である。 プログラミング入門 (前川) 75 リスト 6.12 int 型配列の内容とアドレスを印字するプログラム address 1.c。 #include <stdio.h> #include <stdlib.h> #define SIZE 10 int main(void) { int int_array[SIZE]; int i, min = 100, max = 999; printf("int_array(配列名) の %%p 表示=%p\n", int_array); for(i=0; i<SIZE; i++) int_array[i] = min + rand() % (max - min + 1); printf("配列要素\t: アドレス\t\t: 内容\n"); for(i = 0; i < SIZE; i++) printf("int_array[%2d]\t: %p\t: %5d\n", i, &(int_array[i]), int_array[i]); printf("i\t\t: %p\n", &i); printf("min\t\t: %p\n", &min); printf("max\t\t: %p\n", &max); return 0; } このプログラムでは、ループに入る前に「配列名」が示すアドレスを表示し、次に最初の for ループでその 配列に 100 以上 999 以下の乱数値を設定している。次いで、二つ目の for ループ内で、 printf("int_array[%2d]\t: %p\t: %5d\n", i, &(int_array[i]), int_array[i]); によって各要素のアドレスと整数値を表示している。つまり、最初の %2d で配列の添字 i の値を表示し、配 列要素&(int_array[i]) のアドレスとその値を、二番目の %p と三番目の %5d で、それぞれ表示している。 ' $ int_array(配列名) の %p 表示=0x7fffdaf91730 配列要素 : アドレス : 内容 int_array[ 0] : 0x7fffdaf91730 int_array[ 1] : 0x7fffdaf91734 int_array[ 2] : 0x7fffdaf91738 int_array[ 3] : 0x7fffdaf9173c int_array[ 4] : 0x7fffdaf91740 int_array[ 5] : 0x7fffdaf91744 int_array[ 6] : 0x7fffdaf91748 int_array[ 7] : 0x7fffdaf9174c int_array[ 8] : 0x7fffdaf91750 int_array[ 9] : 0x7fffdaf91754 i : 0x7fffdaf9172c min : 0x7fffdaf91728 max : 0x7fffdaf91724 % & : : : : : : : : : : 983 386 577 215 393 935 686 292 349 821 図 6.6 int 型配列の内容とアドレスを印字するプログラムの実行結果。 % プログラミング入門 (前川) 76 図 6.6 を見ると、配列名 int_array をアドレスと見做して %p 表示した値は 0x7fffdaf91730 で、先頭の配 列要素 int_array[0] のアドレスと同じであることが分かる。また、(この計算機では)int 型配列の各要素は 4 バイトごとに並んでいることも確認できる。なお、 各データ型のサイズ (バイト数) は sizeof 演算子で求め ることができる。 6.4.3 char 型配列 次に示すのは、char 型配列について、上と同様の表示をするプログラムとその実行結果である。 文字そのものを打ち出すための変換子 %c に注意し、前のソースコードと比較し、違いを確認しておこう。 リスト 6.13 char 型配列の内容とアドレスを印字するプログラム address 2.c。 #include <stdio.h> int main(void) { char chr_array[] = "Hello, world!"; int i, array_size = sizeof(chr_array)/sizeof(chr_array[0]); printf("chr_array(配列名) の %%p 表示=%p\n", chr_array); printf(" 配列要素 : アドレス : 内容\n"); for(i = 0; i < array_size; i++) printf("chr_array[%2d]:%p:%2c(%2x)\n", i,&(chr_array[i]),chr_array[i],chr_array[i]); return 0; } ' $ % gcc -o adr2 address_2.c % ./adr2 chr_array(配列名) の %p 表示=0x7fff5ae696c0 配列要素 : アドレス : 内容 chr_array[ 0]:0x7fff5ae696c0: H(48) chr_array[ 1]:0x7fff5ae696c1: e(65) chr_array[ 2]:0x7fff5ae696c2: l(6c) chr_array[ 3]:0x7fff5ae696c3: l(6c) chr_array[ 4]:0x7fff5ae696c4: o(6f) chr_array[ 5]:0x7fff5ae696c5: ,(2c) chr_array[ 6]:0x7fff5ae696c6: (20) chr_array[ 7]:0x7fff5ae696c7: w(77) chr_array[ 8]:0x7fff5ae696c8: o(6f) chr_array[ 9]:0x7fff5ae696c9: r(72) chr_array[10]:0x7fff5ae696ca: l(6c) chr_array[11]:0x7fff5ae696cb: d(64) chr_array[12]:0x7fff5ae696cc: !(21) chr_array[13]:0x7fff5ae696cd: ( 0) % & 図 6.7 char 型配列の内容とアドレスを印字するプログラムの実行結果。 % 図 6.7 を見ると、配列名 char_array のアドレスは 0x7fff5ae696c0 で、先頭の配列要素 char_array[0] の アドレスと同じであることが分かる。また、char 型配列の各配列要素は 1 バイトごとに並んでいることも確 認できる。 プログラミング入門 (前川) 77 6.4.4 int 型配列,char 型配列の比較 以上を整理してみると、図 6.8 のようになる。同図左に int 型配列、右に char 型配列を、それぞれ示す。メ モリセルを表す “箱” は、左が 4 バイト幅、右が 1 バイト幅である。 アドレス例 配列要素 内容 7fff5ae696c0 chr_array[ 0] H アドレス例 配列要素 内容 c1 chr_array[ 1] e 7fffdaf91730 int_array[0] 983 c2 chr_array[ 2] l 34 int_array[1] 386 c3 chr_array[ 3] l 38 int_array[2] 577 c4 chr_array[ 4] o 3c int_array[3] 215 c5 chr_array[ 5] , 40 int_array[4] 393 c6 chr_array[ 6] ␣ 44 int_array[5] 935 c7 chr_array[ 7] w 48 int_array[6] 686 c8 chr_array[ 8] o 4c int_array[7] 292 c9 chr_array[ 9] r 50 int_array[8] 349 ca chr_array[10] l 54 int_array[9] 821 cb chr_array[11] d (4 バイト幅) cc chr_array[12] ! cd chr_array[13] \0 (1 バイト幅) 図 6.8 int 型配列と char 型配列の比較。 同じことだが、メモリイメージを横に配置し、かつ 1 バイトの幅を単位として描いたのが、次の 図 6.9 だ。 int 型配列 int_array char 型配列 chr_array 983 H e 386 l l o , ␣ ··· 577 w o r l d ··· 図 6.9 int 型配列と char 型配列の比較-その 2。 これらの図は、変数や配列のメモリ内における配置とアドレスについて理解を深めるためのものである。こ れらによって、メモリ内における変数や配列のイメージを一旦把握したら、あとは特に必要が無い限り、それ らの変数や配列要素がどのような順序で何バイトを占めているか、などはあまり気にすることはない。 参考文献 [1] B.W.Kernighan, Rob.Pike(福 崎 俊 博), “The Practice of Programming”(プ ロ グ ラ ミ ン グ 作 法), p.43, ASCII(2000).
© Copyright 2024 Paperzz