第 1 章
見えないエラー
とある砂漠に、人間をも喰いつくす妖怪がひそんでいる蟻地獄があります。その蟻地獄 は、人間の目では見えません。あなたは勇気を奮ってその砂漠を横断できますか? 本章では、たった一行のヘッダを題材として、目に見えないエラーや見えにくいエラー などの《落とし穴》を紹介します。 もっとも、プログラミングの落とし穴というものは、本質的に見えにくいものですが…。1
1-1 見えないエラー
本節 、私 気付 中 忍 込 〔見 〕 〔見 〕 、実例 学習 。見えないエラー
List 1-1 示 "max.h" 、受 取 二 引数 a, b 、大 方 値 返 関数形式 max 定義 。 List 1-1 /* 関数形式マクロmaxを定義するヘッダ "max.h"(見えないエラーが潜む) */#define max(a, b) ((a) > (b) ? (a) : (b))
、max 利用 例 List 1-2 。 List 1-2 /* 関数形式マクロmaxを利用するプログラム (見えにくいエラーが潜む) */ #include <stdio.h> #include "max.h" int main(void) { int x, y;
printf("xの値は:"); scanf("%d", &x); printf("yの値は:"); scanf("%d", &y); printf("max(x, y) = %d\n", max(x, y)); return (0); } 、 "max.h" 対 、 エラー 予期しない EOF です。 表示 。『予期 終端 。』 、 必要 〔何 〕 欠落 推測 。 ▼ 具体的なメッセージは、処理系によって異なります。本書に示すエラーメッセージや警 告メッセージは、一例です。 潜在 〔見 〕 、印刷
1
眺 、 誤 分 。
"max.h" 内部 覗 。 、Fig.1-1 。
定義 #define指令行 末尾 改行文字 、 終端
。
、#define #include 前処理指令(preprocessing directive) 、改行文字
終 。正 、 示 実現 。 重 要 前処理指令行の末尾には、必ず改行文字を付けよう。 考 、 〔重要〕 少 変 。最後 行 除 途中 行 、改行文字 必 入 。 、前処理指令 、終端 表 改行文字 必要 。 早速〔重要〕 。 重 要 ヘッダを含めソースファイルの最後の行にも必ず改行文字を付けよう。 不正 、一部 処理系 許容 、 処理系 可搬性 損 。 『私 処理系 使 。』 『私 使 、 用 処理系 関係 。』 感 、可搬性 重要性 受 入 人 、 多 。 、 趣旨 手紙 何通 。 、本当 。 、一部 国 通用 、全世界 通用 、同程度 労力 出費4 4 4 4 4 4 4 4 4 入手 、 選 ? 開発時 、 余計 範囲 構 、次 心 。 重 要 プログラムはできるだけ可搬性が高くなるように実現せよ。 Fig.1-1 ヘッダの実現 #define max(a, b) ((a) > (b) ? (a) : (b)) EOF
#define max(a, b) ((a) > (b) ? (a) : (b)) Ÿ EOF
不正なヘッダ
正しいヘッダ
注: EOFはファイルの終端を、Ÿ は改行文字を表す。
1
、 許 。 文字 半角 空白文字 使 。 重 要 プログラム中の空白として、日本語文字の空白文字を使ってはいけない。 ▼ 空白文字を□などの記号で代替表示するエディタや、日本語文字上のカーソル幅がそれ に即した大きさで表示されるエディタを使えば、このようなエラーは防げます。 中 空白 利用 、空白文字、改行、水平 、垂直 、書 式送 。 空白類文字(white-space character) 呼 。 ▼ 前処理指令では、# から改行文字の間に利用できる空白類文字は、空白文字と水平 文字のみに限定されます。 字下 使 文字 幅 環境 異 、別 環境 作 表示 印刷 際 、文字 読 。 文字 適当 幅 空白文字 変換 表示 List 1-3 示 。 幅 実行時 指定 、使 勝手 。 ▼ 本プログラムで利用している fopen 関数や fclose 関数などは、第 8 章で学習します。 * 紹介 二 、印刷 眺 、 発見 。処理系 出力 意図 的確 把握 能力 身 付 。 Fig.1-2 不正な空白 □□int x, y; 空白が全角文字。見えにくいエラー
"max.h" 改行文字 挿入 上 、再 List 1-2 。 、今度 、次 出力 。 エラー 不正な文字 '0x81' です。 エラー 不正な文字 '0x40' です。 ▼ メッセージ中の文字コードは、日本語文字コードとして、〔シフト JIS コード〕を利用 している処理系での値です。 経験 人 、決 少 。Fig.1-2 示 、変数 x, y 宣言 、int 左側 空白 、日本語( 全角) 空白 文字 。1
List 1-3 /* detab … 水平タブ文字を展開する "detab.c" */ #include <stdio.h> #include <stdlib.h> /*--- srcからの入力をタブを展開してdstへ出力 ---*/ void detab(FILE *src, FILE *dst, int width) {int ch; int pos = 1;
while ((ch = fgetc(src)) != EOF) { int num;
switch (ch) { case '\t':
num = width - (pos - 1) % width; for ( ; num > 0; num--) {
fputc(' ', dst); pos++;
} break; case '\n':
fputc(ch, dst); pos=1; break; default:
fputc(ch, dst); pos++; break; }
} }
int main(int argc, char *argv[]) {
int width = 8; /* 既定幅は8 */ FILE *fp;
if (argc < 2)
detab(stdin, stdout, width); /* 標準入力 → 標準出力 */ else { while (--argc > 0) { if (**(++argv) == '-') { if (*++(*argv) == 't') width = atoi(++*argv); else { fputs("パラメータが不正です。\n", stderr); return (1); }
} else if ((fp = fopen(*argv, "r")) == NULL) {
fprintf(stderr, "ファイル%sがオープンできません。\n", *argv); return (1);
} else {
detab(fp, stdout, width); /* ストリームfp → 標準出力 */ fclose(fp); } } } return (0); } 本プログラム detab は、オペレーティ ングシステムのコマンドライン上から実 行します。"test.c"という名前のファイ ルをタブ幅 4 で表示するには、次のよう に実行します。 > detab -t4 test.cŸ 指定を省略した場合は、タブ幅は 8 と なります。 複数のファイルを指定すると、連続し て出力されます。
1
見落としやすいエラー
次 取 上 List 1-4 、最初 示 List 1-1 別 作成 。 潜 誤 分 ? List 1-4 /* 関数形式マクロmaxを定義するヘッダ "max.h"(見落としやすいエラーが潜む) */#define max (a, b) ((a) > (b) ? (a) : (b))
、 max 呼 出 箇所 対 、次 表示 。 エラー 不正な構文です。 正確 理解 、2 種類 簡単 復習 。 オブジェクト形式マクロ(object-like macro) 、次 。 時 、TRUE 1 置換 。 #define TRUE 1 ▼ 文字列リテラルや文字定数中の TRUE は置換の対象外となります。 関数形式マクロ(function-like macro) 単純 置換 、引数 含 展開 行 。 ▼ 引数がなく、( ) 内が空となっている関数形式マクロもあります。 両者 区別 基準 単純 。 Fig.1-3 不正なマクロと正しいマクロ
#define max□(a, b) ((a) > (b) ? (a) : (b))
名 後 、空白類文字 続 形式 、( 続 関数形式 。 List 1-4 見 。max ( 間 空白 。 、Fig.1-3 示 、max 形式 、 、
(a, b) ((a) > (b) ? (a) : (b))
置換 。
図 、正 宣言 展開結
果 。
誤 … max はオブジェクト形式マクロ
#define max(a, b) ((a) > (b) ? (a) : (b)) 正 … max は関数形式マクロ
余分な空白文字。 余分な空白文字。 max(x, y)
➡ 置換
(a, b) ((a) > (b) ? (a) : (b))(x, y)
max(x, y) ➡ 展開
1
読 、空白 入 大事 。 、 入 。 重 要 関数形式マクロの定義では、マクロ名と ( の間に空白を入れてはいけない。 、以下 示 例 、関数形式 呼出4 4 4側4 、 名 ( 間 空白 入 構 。z = max (x, y); /* 呼出しでは、maxと(の間に空白があってもよい */ ▼ 関数形式マクロという用語は、《関数と同じように呼び出せる》ことに由来します。 * 識別子 ( ) 囲 呼 出 、 展開 抑制 、max 関数4 4 呼 出 。 z = (max)(x, y); /* マクロではなく関数を呼び出す */ 重 要 識別子を ( ) で囲むとマクロの展開が抑制される。 List 1-5 確認 。 、同一 名前 関数 、関数形式 、使 分 呼 出 利用 。 List 1-5 /* 同名の関数とマクロを呼び分けるプログラム例 */ #include <stdio.h> /*--- マクロ版 ---*/
#define max(a, b) ((a) > (b) ? (a) : (b)) /*--- 関数版 ---*/
int (max)(int a, int b) { puts("関数版maxが呼び出されました。"); return (a > b ? a : b); } int main(void) { int x, y;
printf("xの値は:"); scanf("%d", &x); printf("yの値は:"); scanf("%d", &y);
printf("max(x, y) = %d\n\n", max(x, y)); /* マクロ版を呼び出す */ printf("(max)(x, y) = %d\n", (max)(x, y)); /* 関数版を呼び出す */ return (0); } 実 行 例 xの値は:15Ÿ yの値は:7Ÿ max(x, y) = 15 関数版maxが呼び出されました。 (max)(x, y) = 15
1
前処理指令内の空白
前処理指令内 空白 、私 初 C言語 使 思 出 。 私 人生 初 書 C 1行目 #include <stdio.h> 、『C言語 自由形式 。』 考 、# 左側 空白文字 入 。 、 使用 処理系 、意味不明 出力 、私 受 付 。当時 、前処理指令 # 、行 先頭 位置 規則 定 処理系 多 。 標準C 、 制限 。# 前 、# include 間 、空白文字 水平 文字 。 Fig.1-4 示 、前処理指令 適当 設 、 読 。 Fig.1-4 前処理指令のインデント #if defined(__DOHC__) #include <double.h> #else #include <single.h> #endif#if 指令と注釈
Fig.1-4 利用 #if指令 有効 利用例 存知 。 Fig.1-5 、何 理由 、 Fig.1-5 間違ったコメントアウト /* a = x; /* aにxを代入 */ */ a = x; 文 意図 作 。 、注釈 〔入 子〕 。 注釈 青文字 部分 。 入 子 注釈 許 処理系 存在 、 可搬性 考 、 依存 。 注釈 、 読 人間 伝 情報 与 、 。 Fig.1-6 正しいコメントアウト #if 0 a = x; /* aにxを代入 */ #endif Fig.1-6 、#if 指令 用 記述 最 方 。 条件判定 用 式 値 、0 偽 、網 部 、 時 読 飛 。 重 要 注釈は、プログラムをコメントアウトするためのものではない。プログラムのコ メントアウトには、#if 指令を利用せよ。 これは注釈とはみなされない。 読み飛ばされる。1
処理系 、#if 0 間 #if0 受 付 。 、if 、 続 式( 場合 0) 間 、空 。 重 要 #ifと続く式の間には必ず空白を入れよ。 以上 、Fig.1-7 。# 前 # if 間 空白 入 入 、if 式 間 必 空白 入 。 時 、 一部 / … 頻繁 切 場合 、List 1-6 実現 。 List 1-6 /* #if指令によるプログラムのコメントアウト */ #include <stdio.h> #define DEBUG 0 int main(void) { int a = 5; int x = 1; #if DEBUG == 1 a = x; /* aにxを代入 */ #endif printf("aの値は%dです。\n", a); return (0); } 冒頭 DEBUG 0 定義 、 網掛 部 読 飛 無視 。 部分 読 飛 DEBUG 定義 1 変 (実行結果 )。 重 要 プログラムのデバッグに伴う、プログラム部分の有効化/コメント化の実現には #if指令をうまく利用せよ。 Fig.1-7 #if 指令と空白 □ # □ if □ 式 空白文字・水平タブ文字挿入可。 詰めたらダメ! 実行結果 aの値は5です。 実行結果 aの値は1です。 1に書きかえると1
ヘッダの
実現
#define para 10 #define para 10 #include "max.h" #include "max.h" 付 、 行 。、 "abc.h"中 (max 定義 必要 )"max.h"
。 、
#include "max.h" /* "max.h"を直接インクルード */
#include "abc.h" /* "max.h"を"abc.h"を通じて間接的にインクルード */
、"max.h" 2回 。 右 示 例 、変数 関数 定義4 4 含 複数 回 、 〔再定義〕 。 /* "def.h" */ int a; #include "def.h" #include "def.h" * 何度 問題 List 1-7 示 。 List 1-7 /* 多重のインクルードに対するガードを施した "max.h" */ #if !defined(__MAX) #define __MAX
#define max(a, b) ((a) > (b) ? (a) : (b)) #endif
最初にインクルードされたとき
__MAX 定義 、網掛 部 __MAX max 定義 。
2 回目以降にインクルードされたとき __MAX 定義済 、!defined(__MAX) 判定 成立 、網掛 部 読 飛 。 重 要 ヘッダは、2回目以降のインクルード時に、その本体部分が読み飛ばされるよう に実現せよ。 ▼ 第 7 章では、構造体の宣言を含んだ、より複雑なヘッダの実現法を学習します。 2回目以降は読み飛ばされる。 定義 、(同 限 )何度 行 。 示 、2 回繰 返 構 。 、 示 同一 複数回 。 『 。』 思 、気
1
マクロと
実行効率
max 利用 、四 数値 a, b, c, d 最大値 求 。 x = max(max(a, b), max(c, d)); 展開 Fig.1-8 示 。 何 、 理解 。読 。 呼出 、演算効率 非常 悪 。 、四 数値 最大値 、次 求 。 x = max(max(max(a, b), c), d); 、Fig.1-9 展開 。 、 悪 。 手続 、> 演算子 比較 、 何回行 。 回数 数 、演算効率 悪 明白 。 x = a; if (b > x) x = b; if (c > x) x = c; if (d > x) x = d; Fig.1-8 四値の最大値を求める max の呼出しと展開結果(1) Fig.1-9 四値の最大値を求める max の呼出しと展開結果(2) 〔見 目 気付 問題点〕 。 右 示 、if 文 羅列 実現 方 、演算 効 率 。 4行 見 上 長 。 、何 素直 一番 。 重 要 プログラムの見かけが短い方が、実行時の効率がよいとは限らない。 x = max(max(a, b), max(c, d)); ➡ 展開 x = ((((a) > (b) ? (a) : (b))) > (((c) > (d) ? (c) : (d))) ? (((a) > (b) ? (a) : (b))) : (((c) > (d) ? (c) : (d)))); x = max(max(max(a, b), c), d); ➡ 展開x = ((((((a) > (b) ? (a) : (b))) > (c) ? (((a) > (b) ? (a) : (b))) : (c))) > (d) ? (((((a) > (b) ? (a) : (b))) > (c) ? (((a) > (b) ? (a) : (b))) : (c))) > (d));
理解困難
1
C++ での max の実現
C++ 、C言語 max 実現 。簡単 紹介 。 インライン関数 List 1-8 示 、関数定義 inline 指定子 加 、 関数 インライン 関数(inline function) 。 関数形式 同様 、 中 展開 埋 込 、関数呼出 伴 。 版 同等 高速性 期待 。 ▼ 繰返し文を含むような複雑・大規模な関数は、インラインに展開されない可能性があり ます。その場合は、通常の関数と同じように扱われます。、呼出 max(x++, y) 、((x++) > (y) ? (x++) : (y)) 展開
2回行 副作用 発生 。 、 関数 、
副作用 問題 生 保証 。
示 実現 、int 型 扱 欠点 。
多重定義
引数 型 個数 異 、同一名 関数 複数個定義 関数多重定義( function
overloading) 利用 "max.h" List 1-9 示 。
C++ 特有 定義 名 __cplusplus 、C++ 関数版 提供 、C言語 関数形式 版 提供 。 List 1-8 // // インライン関数maxを定義するヘッダ(C++) "max.h" // //--- インライン関数 ---// inline int max(int a, int b) { return (a > b ? a : b); } List 1-9 /* 関数多重定義によるmaxを定義するヘッダ(C/C++) "max.h" */
#if defined __cplusplus /* C++ */
inline int max(int a, int b) { return (a > b ? a : b); } inline long max(long a, long b) { return (a > b ? a : b); } inline double max(double a, double b) { return (a > b ? a : b); }
#else /* C */
#define max(a, b) ((a) > (b) ? (a) : (b)) #endif
1
関数形式 版 max 、> 演算子 比較可能 型 引数 対 有効
対 、 示 関数版 、適用 型 int, long, double 三
限 欠点 。
関数テンプレート
引数 〔型〕 受 取 関数テンプレート( function template) 利用 、
制約 解放 。
max 関数 定義 "max.h" List 1-10 示 。
List 1-10 /*
関数テンプレートによるmaxを定義するヘッダ(C/C++) "max.h" */
#if defined __cplusplus /* C++ */
template <class Type> Type max(Type a, Type b) {
return (a > b ? a : b); }
#else /* C */
#define max(a, b) ((a) > (b) ? (a) : (b)) #endif ▼ C++ では関数テンプレートを提供し、C言語では関数形式マクロ版を提供します。 List 1-11 // // 関数テンプレートmaxを利用するプログラム(C++) // #include <iostream> #include "max.h" using namespace std; int main(void) { int x, y; cout << "xの値は:"; cin >> x; cout << "yの値は:"; cin >> y;
cout << "max(x, y) = " << max(x, y) << endl; return (0); } 実 行 例 xの値は:15Ÿ yの値は:7Ÿ max(x, y) = 15 関数 呼 出 、呼 出 側 型 即 関数 実体 、 自動的 生成 。 、int 用、double 用 具合 、個別 関 数 作 手間 解放 。 利用 例 List 1-11 示 。
1
1-2 初期化
匿名希望 読者 方 、次 質問 。 友人の作ったプログラムを参考にしながらC言語の学習を進めています。友人のプ ログラムには、初期化していないにもかかわらず値が 0 になっている変数と、そうで ない変数があるようです。これらの違いを教えてください。 変数 初期化 怠 、私 起 。軽微 作業 、 実行上 重大 。 本節 、初期化 学習 。初期化と代入
変数 〔初期化〕 聞 、次 宣言 思 浮 。 int x = 5; 初期値 与 5 初期化子(initializer) 呼 、x 5 初期化 。 、List 1-12 初期化子 { } 囲 宣言 。 List 1-12 /* 単一のオブジェクトを{ }で囲んだ初期化子によって初期化 */ #include <stdio.h> int main(void) { int n = {4.5}; printf("n = %d\n", n); return (0); } ▼ このプログラムがコンパイル不能であったり、おかしな実行結果が得られるのであれば、 その処理系は標準Cに準拠していないことになります。 、次 宣言 考 。 int y = 97.2; int 型 取 扱 小数点以下 部分 切 捨 、y 初期値 97.2 97 。 、次 。 重 要 初期化子の値がそのまま初期値になるとは限らない。 実 行 結 果 n = 41
初期化 代入 、見 似 、値 入 4 4 4 4 4 違 。 変数 生成 値 入 〔初期化〕 、 生成 変数 値 入 〔変数〕 〔 〕 関係 、名前(name) 識別子(identifier) 関係 似 。 、後者 正式 用語 、厳密 区別 必要 文脈 、前者 使 構 。C言語 、B. W. Kernighan and Dennis M. Ritchie The C Programming Language
、variable(変数) object( ) 混同 使 。用語 不統 一 揚 足 取 人 … 。 * 、 初期化 、 寿命 決定 記憶域期間 、深 関連 。復習 兼 、 学習 。 Fig.1-10 オブジェクトの定義 その内容によって、値を表現することができる実行環境中の記憶域の領域。ビットフィー ルド以外のオブジェクトは、連続する一つ以上のバイトからなる。そのバイトの個数、順 序および符号化規則は、明示的に規定するかまたは処理系定義とする。 オブジェクトを参照する場合、オブジェクトは、特定の型をもっていると解釈してもよい。 int m = 3; /* 初期化 */ int x; /* … */ x = 0; /* 代入 */ 〔代入〕 。 重 要 初期化は、変数の生成時に値を格納する操作であり、既に生成されている変数に 値を格納する代入とは異なる。
オブジェクト
次 示 二 宣言 考 。 const int a; /* 定数変数(?)*/ int b; /* 変数 */ 存知 、a 値 変更 。変数(variable) 語句 、値 可変 意味 、a 変数 呼 適当 。 、a 〔定数〕 呼 。整数定数 1053、浮動小数点定 数 32.5、文字定数'x' 、値 直接表現 〔定数〕 。 正式 用語 、オブジェクト(object) 。a b 。 標準C 定義 Fig.1-10 示 。『値 、適切 大 記憶域』 理解 。1
自動記憶域期間をもつオブジェクトの初期化
List 1-13 見 。 List 1-13 /* 自動記憶域期間をもつオブジェクトの初期化 */ #include <math.h> #include <stdio.h> void func(int no) {register int i; auto int x = 100; printf("x = %d\n", x); for (i = 0; i < no; i++) {
double x = sin((double)i / no); printf("x = %f\n", x); } printf("x = %d\n", x); } int main(void) { func(10); return (0); } 関数 func 仮引数 no 、関数内 定義 変数 i x 、 関数 実行中 存在 。関数 func 実行 終了 消 。 、宣言 { … } 抜 出 生 寿
命 自動記憶域期間(automatic storage duration) 。
自動記憶域期間 与 Fig.1-11 示 。 ■ 関数が受け取る仮引数 ■ 関数の中で、以下のように定義されたオブジェクト ・記憶域クラス指定子なしで定義されたオブジェクト ・記憶域クラス指定子 auto を伴って定義されたオブジェクト ・記憶域クラス指定子 register を伴って定義されたオブジェクト Fig.1-11 自動記憶域期間をもつオブジェクト ▼ 記憶域クラス指定子 auto は、省略しても同じです。register は、『高速にアクセスで きるレジスタに割り当てた方がいいかもしれませんよ。』と処理系に示唆するものです。 ただし、register 宣言されたオブジェクトが、レジスタに格納されるとは限りません。 実 行 結 果 x = 100 x = 0.000000 x = 0.099833 x = 0.198669 x = 0.295520 x = 0.389418 x = 0.479426 x = 0.564642 x = 0.644218 x = 0.717356 x = 0.783327 x = 100
1
関数 func 、二 x 。最初 宣言 x 、関数末尾 } 寿 命 、for 文 中 宣言 x 、for 文末尾 } 寿命 。 ▼ 同一名の識別子が複数ある場合、より内側のブロックで宣言されたものが〔見える〕よ うになり、外側のものが一時的に〔見えなく〕なります。 このことは、実行時のオブジェクトの寿命である〔記憶域〕の問題ではなく、ソースプ ログラム上での識別子が通用する範囲である〔スコープ〕の問題です。 for 文内 宣言 x 初期化子 、関数呼出 式 。 、自動記憶域期間 初期化子 、定数 構 。x 、for 文 繰返 毎回生成 、sin((double)i / no) 返却
値 初期化 。 生成 初期化 、以下 理解 。 重 要 自動記憶域期間をもつオブジェクトは、プログラムの流れがその宣言を通過する ときに生成されて初期化される。 * 明示的 初期化子 与 自動記憶域期間 、不定値 初期化 。 重 要 初期化子が与えられていない自動記憶域期間をもつオブジェクトは、不定値で初 期化される。 、List 1-14 確認 。 ▼ 実行によって表示される値は不定です。 List 1-14 /* 初期化子の与えられていない自動記憶域期間をもつオブジェクトが 不定値すなわちゴミの値で初期化されることを確認 */ #include <stdio.h> int main(void) { int x; /* 不定値で初期化される */ printf("x = %d\n", x); return (0); } 実行結果一例 x = 957 変数 x 初期値 957 、-38 。 、 0 。
1
静的記憶域期間をもつオブジェクトの初期化
自動記憶域期間 対照的 寿命 静的記憶域期間(static storage duration) 。
違 List 1-15 学習 。 List 1-15 /* オブジェクトの記憶域期間(静的/自動)と初期化 */ #include <stdio.h> int ft = 0; /* 静的記憶域期間 */ void func(void) { int at = 0; /* 自動記憶域期間 */ static int st = 0; /* 静的記憶域期間 */ ft++; at++; st++; printf("ft = %d at = %d st = %d\n", ft, at, st); } int main(void) { int i; for (i = 0; i < 8; i++) func(); return (0); } 関数 外 定義 ft 、各関数 実行 無関係 、 起動時 終 了時 存在 。 静的記憶域期間 。 、st 関数内 static 記憶域 指定子 加 宣言 静的記憶域期間 与 。 静的記憶域期間 与 Fig.1-12 示 。 実行 通 生 続 。初期化 、一度 行 、 流 宣言 通過 行 。 重 要 静的記憶域期間をもつオブジェクトは、プログラム開始の準備段階の生成時に、 一度だけ初期化され、プログラムの実行を通じて生き続ける。 ■ 関数の外で定義されたオブジェクト ■ 関数の中で static を伴って定義されたオブジェクト Fig.1-12 静的記憶域期間をもつオブジェクト 実 行 結 果 ft = 1 at = 1 st = 1 ft = 2 at = 1 st = 2 ft = 3 at = 1 st = 3 ft = 4 at = 1 st = 4 ft = 5 at = 1 st = 5 ft = 6 at = 1 st = 6 ft = 7 at = 1 st = 7 ft = 8 at = 1 st = 8
1
□静的記憶域期間 関数 func 呼出 関係 、変数 ft st 、 実行 通 値 保 持 。最初 0 初期化 、関数 呼 出 、 値 、関数 func 呼 出 回数 。 □自動記憶域期間 変数 at 、関数 func 呼 出 流 宣言 通過 、生成 0 初期化 。 静的記憶域期間 変数 ft st 宣言 初期化 子 取 除 、 右 変更 。『ft st 不定値 初期化 。』 心配 、変更前 同 実行結果 得 。 、次 理由 。 重 要 初期化子が指定されていない静的記憶域期間をもつオブジェクトは、0 で初期化 される。 void func(void) { int x = sin(0.9); /* OK */ static int st = sin(0.9); /* エラー */ } 、静的記憶域期間 初期化子 、定数式 。 宣言 。 * 二 記憶域期間 関 、 Table 1-1 。 ▼ もう一つの記憶域期間である割付け記憶域期間は、第 3 章で学習します。 int ft; void func(void) { int at = 0; static int st; /* (中略) */ } ■Table 1-1 記憶域期間とオブジェクトの初期化 自動記憶域期間 静的記憶域期間 生 成 プログラムの流れが、その宣言を通過する際に生成される。 main備段階において生成される。関数実行開始前のプログラム準 初 期 化 明示的に初期化しなければ不定値で初期化される。 明示的に初期化しなければ 0 で初期化される。 初期化子 定数式でなくともよい(ただし配列・ 構造体は除く)。 定数式でなければならない。 破 棄 その宣言を含むブロックを抜け出る際に破棄される。 プログラム実行終了後の後始末の段階で破棄される。1
識別子の有効範囲と初期化
宣言 識別子 、 宣言子 直後、 初期化子 前 時点 、 名 前 使 。 List 1-16 確認 。 List 1-16 /* 不定値である自分自身の値で初期化 */ #include <stdio.h> int x = 1; int main(void) { int x = x; /* 自分自身の値で初期化 */ printf("x = %d\n", x); return (0); } ▼ 実行によって表示される値は不定です。 List 1-17 /* 変数の有効範囲と初期化 */ #include <stdio.h> int z = 1; int main(void) { int x = z; int z = 0; int y = z; return (0); } 実行結果一例 x = 9572 main 関数 変数 x 宣言 初期化子 x 、 宣言 x自身 、関数 外 定義 x 。 自動記憶域期間 変数 x 初期値 不定値 、x 不定値 初期化 。 、 初期化子 蛇足 、何 意味 。 List 1-17 各変数 、 値 初期化 。 課題 、考 。 実 行 結 果 printf関数の呼出しを追 加して、x, y, zの値を 確かめてみましょう。1
C++ の初期化子
C++ 、以下 二 宣言 同 。 int i = 5; /* 形式A:CとC++に共通 */ int i(5); // 形式B:C++特有でC言語では不可 、List 1-18 確認 。実行 。 List 1-18 // // ( )形式の初期化子によって初期化(C++) // #include <iostream> using namespace std; int main(void) { int x = 5; /* xを5で初期化 */ int y(5); /* yを5で初期化 */ cout << "x = " << x << endl;cout << "y = " << y << endl; return (0);
}
形式B 、 初期化 共通性 導入 。
以下 示 Complex 例 、要点 説明 。
class Complex { // 複素数クラス
double re, im; public: Complex(double r, double i = 0.0) // コンストラクタ { re = r; im = i; } // ... }; Complex型 宣言例 示 。 Complex a(5.0, 7.5); // 宣言X:引数は二つ Complex b(5.0); // 宣言Y:引数は一つ ( ) 中 、 区切 引数 並 、 渡 。 、引 数 一 場合 、次 ( ) 使 宣言 。 Complex b = 5.0; // 宣言Z:引数は一つ 〔宣言Y〕 形式B 、〔宣言Z〕 形式A 。int 型 double 型 基本型 、 型 同 初期化・宣言 。 実 行 結 果 x = 5 y = 5
1
配列の初期化
配列 初期化 行 典型的 宣言例 示 。
int a[3] = {1, 2, 3};
青文字 {1, 2, 3} 配列 a 対 初期化子 。 中 1, 2, 3 各要素 対
初期化子 、配列 要素 a[0], a[1], a[2] 、順 1, 2, 3 初期化 。
List 1-19 ・実行 。 List 1-19 /* 自動記憶域期間をもつ配列を初期化 */ #include <stdio.h> int vx[3] = {1, 2, 3}; /* 静的 */ int main(void) { int i; int ma[3] = {1, 2, 3}; /* 自動:K&Rでは不可 */ static int ms[3] = {1, 2, 3}; /* 静的 */ for (i = 0; i < 3; i++) printf("vx[%d] = %d ma[%d] = %d ms[%d] = %d\n",
i, vx[i], i, ma[i], i, ms[i]); return (0); } K&R 、自動記憶域期間 配列 初期化子 与 。 関数中 static 与 配列 初期化 不可能 。 、標準C 、 制限 。 配列 ma 宣言 対 、次 出力 処理系 、標準C 準拠 。 エラー 自動記憶域期間をもつ配列の初期化はできません。 List 1-20 進 。要素数 3 配列 b 対 、初期化子 一 与 。 場合、次 規則 適用 。 重 要 配列に対する初期化子の個数が、配列の要素数に満たないとき、初期化子が与え られていない要素は 0 で初期化される。 、Fig.1-13 解釈 。初期化子 与 b[1] b[2] 0 初期化 処理系 、標準C 準拠 。 全要素 0 初期化 宣言 Fig.1-14 示 。非常 短 宣言 。 、初期化子 配列 要素数 多 許 。 、要素 実 行 結 果 vx[0] = 1 ma[0] = 1 ms[0] = 1 vx[1] = 2 ma[1] = 2 ms[1] = 2 vx[2] = 3 ma[2] = 3 ms[2] = 3
1
数 2 配列 対 、三 初期化子 与 宣言 。 int c[2] = {1, 2, 3}; /* エラー */ 配列 対 全 初期化子 与 、次 宣言 。 int d[3]; 関数 外 定義 、関数内 static 付 定義 〔静的記憶域期間〕 与 、全要素 0 初期化 。 関数内 static 伴 定義 、〔自動記憶域期間〕 与 、 全要素 初期値 不定値 。 、単独 場合 同 。 、自動記憶域期間 、配列 対 初期化子 、必 定数式 (単独 、 :p.17)。 、以下 質問 何度 。 List 1-20 /* 初期化子が与えられていない要素が0で初期化されることを確認 */ #include <stdio.h> int main(void) { int i; int b[3] = {1}; /* 先頭から順に1,0,0で初期化される */ if (b[1] != 0 || b[2] != 0) puts("正しく初期化されていません。"); else for (i = 0; i < 3; i++) printf("b[%d] = %d\n", i, b[i]); return (0); } Fig.1-13 省略された初期化子 Fig.1-14 すべての要素を 0 で初期化 int x[3]; /* … */ x = {0, 1, 2}; /* エラー */ 右のプログラムのように、配列の複数要素に一括 して値を代入しようとすると、エラーになるのはど うしてですか。 { … } 、初期化子 特別 構文 、 以外 使 。C言語 、 配列 一括 値 代入 。 実 行 結 果 b[0] = 1 b[1] = 0 b[2] = 0 int b[3] = {1}; ➡ このように解釈される int b[3] = {1, 0, 0}; int x[1000] = {0}; 全要素を0で初期化するには、 一つだけ0を与えればよい。1
多次元配列の初期化
多次元配列 初期化 関 、1 次元配列 対 初期化 規則 再帰的 適用 。List 1-21 例 考 。 配列 x 中 要素 、Fig.1-15 示 、x[0][0], x[0][1], x[1][0], x[1][1], x[2][0], x[2][1] 順 、記憶域上 並 。 要素 対 初期化子 与 、 順 初期化 。 Fig.1-15 2次元配列の初期化(その1) int x[3][2] = {{0, 1}, {2, 3}, {4, 5}, }; x[0][0] x[0][1] x[1][0] x[1][1] x[2][0] x[2][1] 0 1 2 3 4 5 ➡ ➡ ➡ ➡ ➡ ➡ List 1-21 /* 2次元配列の初期化を確認 */ #include <stdio.h> int main(void) { int i, j; int x[3][2] = {{0, 1}, {2, 3}, {4, 5}, }; for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) printf("x[%d][%d] = %d\n", i, j, x[i][j]); return (0); } 宣言 初期化子 横 並 。 int x[3][2] = { {0, 1}, {2, 3}, {4, 5}, }; 最後 文字 , 余計 感 。 取 、 int x[3][2] = { {0, 1}, {2, 3}, {4, 5} }; 、 同 初期化 行 。 最後 、初期化子 縦 並 際 見 上 、 構 。 初期化子 構文 、Fig.1-16 示 複雑 。 実 行 結 果 x[0][0] = 0 x[0][1] = 1 x[1][0] = 2 x[1][1] = 3 x[2][0] = 4 x[2][1] = 51
Fig.1-16 初期化子の構文図 図 示 、1 次元配列 初期化 、次 宣言 。 int d[3] = {1, 2, 3,}; /* 最後の要素の後に,があってもよい */ * 配列 対 初期化子 不足 、 要素 0 初期化 規則 、 多次元配列 成立 。Fig.1-17 確認 。 、多次元配列 対 初期化子 、必 { } 入 子 必要 。 Fig.1-18 示 、先頭 順 初期化 行 。 Fig.1-17 2次元配列の初期化(その2) Fig.1-18 2次元配列の初期化(その3) int x[3][2] = { {0}, {2, 3}, {4}, }; int x[3][2] = {0, 1, 2}; 式 { 初期化子 } , , x[0][0] x[0][1] x[1][0] x[1][1] x[2][0] x[2][1] 0 0 2 3 4 0 ➡ ➡ ➡ ➡ ➡ ➡ x[0][0] x[0][1] x[1][0] x[1][1] x[2][0] x[2][1] 0 1 2 0 0 0 ➡ ➡ ➡ ➡ ➡ ➡ int x[3][2] = { {0, 0}, {2, 3}, {4, 0}, }; int x[3][2] = { {0, 1}, {2, 0}, {0, 0}, }; ➡ このように解釈される ➡ このように解釈される ▼ 文字列の初期化については第 4 章で、構造体や共用体の初期化については第 7 章で学 習します。1
typedef 名が与えられた配列の初期化
typedef 名 与 配列 初期化 考 。 、typedef 宣言 何 思 出 。 重 要 typedef宣言は、新しい型を作るのではなく、既存の型に新しい名前を与える宣 言である。 、typedef int INTEGER; /* INTEGERはintの同義語 */
宣言 、 以降、INTEGER int 同 意味 。 、
INTEGER a; /* aは実質的にint型 */
、以下 宣言 実質的 同 。 int a; /* aはint型 */ 、typedef 宣言 〔新 型 作 宣言〕 、多 人 勘違 。正 理解 。 * 、List 1-22 示 考 。要素型 int 要素数 5 配列型 対 、Int5ary typedef名 与 。 List 1-22 typedef 名 与 配列 初期化 、基本的 、通常 配列 同様 。 要素数 5 配列 x 対 、初期化子 三 与 、不足 部分 要素 0 初期化 。 、実行結果 分 。 、 網掛 部 宣言 、実質的 、 int x[5] = {1, 2, 3}; 同一 。 /* typedef名による配列の初期化を確認 */ #include <stdio.h> int main(void) { int i;
typedef int Int5ary[5]; /* 要素型がintで要素数が5の配列型 */ Int5ary x = {1, 2, 3}; for (i = 0; i < 5; i++) printf("x[%d] = %d\n", i, x[i]); return (0); } 実 行 結 果 x[0] = 1 x[1] = 2 x[2] = 3 x[3] = 0 x[4] = 0
1
次 、List 1-23 示 考 。 List 1-23 /* typedef名による不完全な配列の初期化を確認 */ #include <stdio.h> int main(void) { int i;typedef int IntAry[]; /* 要素型がintの配列型 */ IntAry a = {1, 2, 3}; /* 要素数は3 */ IntAry b = {1, 2, 3, 4, 5}; /* 要素数は5 */ for (i = 0; i < 3; i++) printf("a[%d] = %d\n", i, a[i]); for (i = 0; i < 5; i++) printf("b[%d] = %d\n", i, b[i]); return (0); }
、要素型 int 配列型 対 、IntAry typedef名 与
。 、要素数 与 。 配列 a b 、 IntAry型 宣言 。 与 初期化子 個数 配列 要素数 決定 、配列 a 要素数 3、 配列 b 要素数 5 。 、a, b 宣言 、実質的 、 int a[3] = {1, 2, 3}; int b[5] = {1, 2, 3, 4, 5}; 同 。 ▼ IntAry は、要素数が不定の不完全型です。不完全型のオブジェクトを作り出すことは できません。初期化子の個数から要素数が決定して完全型となります。 実 行 結 果 a[0] = 1 a[1] = 2 a[2] = 3 b[0] = 1 b[1] = 2 b[2] = 3 b[3] = 4 b[4] = 5