Subsections


20章 C 言語 II


20.1 ANSI C, C99: C 言語の規格

C 言語の規格は大きく分けると3つの段階を経てきた. 時間的な順序で並べると以下のようになる. 下線部は俗称. 特に, 関数の定義の仕方に特徴があるので各規格ごとに関数の定義の部分を載せる. 文献については, [第25章]を参照のこと.

(1) K&R

関数は次のように定義される.

func(a, b)
    int a;
    double b;
{
    /* 関数本体 */
    return 0;
}

(2) ANSI C, ISO C, JIS C, C90
言語仕様が国際的な規格として制定された.

関数の定義の仕方が下のようになり, 引数の型のチェックが厳密になった. 指定しない場合は int型とみなされる.

func(int a, double b)
{
    /* 関数本体 */
    return 0;
}

(2') C95
C90への若干の追加. ワイドキャラクタの追加[20.10]など.

(3) C99
C90から比べて, 以下の機能が追加された.

int 型であっても明示的に指定する必要がある.

int func(int a, double b)
{
    /* 関数本体 */
    return 0;
}

現在使用しているコンパイラーの対応状況は, 標準定義マクロ[20.11.1]により調べることができる.

規格票の入手先は, 「参考となる文献」[第25章]を参照のこと.

20.2 C 言語の構造


20.2.0.1 名前

変数名, 関数名, ラベル 等につけることができる名前は次のようなものです.

慣習として,


20.2.0.2 予約語

次のものが予約語である.

auto, break, case, char, continue, default, do, double, else, enum, extern, float, for, goto, if, int, long, register, return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, while


20.2.0.3 プログラムのフォーマット

#include <stdio.h>

int main(void)
{
    int x;

    x = 10;
    printf("x = %d", x);

    return 0;
}
は次のように書いても文法上は間違いではない.
#include <stdio.h>int main(void){int x;x=10;
printf("x = %d",x);return 0;}
しかし, 可読性が全く異なる. 複雑な構造を持つプログラムでは, どこか らどこまでが1つのブロックなのかを, 視覚的に表現したほうが可読性が高い. ここの例では {} で囲まれた一つのブロックを 視覚的に表現している. 対応する {} を左から数えたら 同じ深さの所に書き, その内部は全て 4 文字分 字下げ(インデント)したとこ ろから書き始めている. このような字下げは「タブ」キーを使って行う.

インデントには色々な流儀があるが, まずは古くから広く使われている K & R がいい. emacs では, タブキーを押した ら自動的に K & R に従ったインデントが行なわれるようにできる. それには次のように .emacs 中で 'c-mode-hook(c-set-style "k&r") を加えおく.

(add-hook 'c-mode-hook
          '(lambda () 
             (set-buffer-file-coding-system 'euc-japan)
             (c-set-style "k&r")))

ブロックの中に又別のブロックがあったらその中を更に深く字下げする.

#include <stdio.h>

int main(void)
{
    int x;

    for(x=0; x<10; x++){
        y = x*x;
        printf("%d^2 = %d\n", x, y);
    }

    return 0;
}

深さが分かるように書き直すと次のようになる.

/* +---+---+---+---+---+---+---+---+--- */
#include <stdio.h>

int main(void)
{
/* +---+---+---+---+---+---+---+---+--- */
    int x;

/* +---+---+---+---+---+---+---+---+--- */
    for(x=0; x<10; x++){
/* +---+---+---+---+---+---+---+---+--- */
        y = x*x;
        printf("%d^2 = %d\n", x, y);
/* +---+---+---+---+---+---+---+---+--- */
    }

/* +---+---+---+---+---+---+---+---+--- */
    return 0;
}


20.3 データ


20.3.1 基本データ型

基本データ型

char
文字
int
整数
float
単精度浮動小数点数
double
倍精度浮動小数点数

基本データ型を次の修飾子で修飾できる.

long
サイズが大きい.
short
サイズが小さい.
signed
符合付き.
unsigned
符合無し.

これらのオブジェクトのサイズや表現できる範囲は, コンパイラーや機種に依 存する. それらの情報は <limits.h> や <float.h> のヘッダーファイル及び sizeof 演算子20.4.1で得られる.


20.3.2 定数

型 (printf での指定) と 例

10進 整数 (%d, %u)
「1」, 「-3」

8進 整数
前に 0 を付ける. 例:「0777」.

16進 整数
前に 0x を付ける. 例:「0x1a」.

倍精度整数
後ろに L か l を付ける. 例:「10L」「0777L」「0x1aL」.

浮動小数点数 (%g, %e, %f)

文字 (%c)

文字列 (%s)


20.3.3 変数の有効範囲と構造化

C 言語では, 関数呼び出しを行なっても呼び出し側の変数の値は特に指定しな いかぎり変更されない. 従って呼び出す関数の内部の構造を知る必要はなく, 入力と出力だけを知っておけばよい. このような関数のブラックボックス化 (構造化) によりプログラムの作成効率, 保守性, 可読性がが飛躍的に向上す る. ここでは, このような考え方を実現している 局所的な変数(auto 変数) について説明する.

20.3.3.1 auto 変数

ブロック内で宣言されている変数は そのブロックに入った時から そのブロッ クを出る時まで存在する. このような変数は 後述する static 変数に対し auto 変数という. 次の int a; は 「{」 と 「}」 で 囲まれた main 関数内で寿命を持つ.

int main(void)
{
    int a;

    /* */
    return 0;
}

関数 func 内で宣言している変数int b はその関数が呼び出さ れてから, 関数を抜け出す時まで存在している.

#include <stdio.h>

/* int b; の領域が存在するのは */
void func(void)
{ /* ここから */
    int b;

    b = 3;
    printf("b in func = %d\n", b);
  /* ここまで */
}

int main(void)
{
    int a;

    a = 2;
    printf("a in func = %d\n", a);

    func();
    return 0;
}


20.3.3.2 auto 変数の有効範囲

変数の型宣言を行うと, その変数に必要な領域がメモリ上に確保される.

型宣言はメモリ上に領域を確保することを指示する命令である. 変数の有効範囲は変数が宣言されて以降で, ブロック{ }の中のみである.

関数の内部で宣言された変数は, その関数の内部でのみ有効. 他の場所で同じ名前の変数が使われていても, 遮蔽されていて相互に影響を及ぼさない. 次の例では, func 内の a と main 内の a が, 名前が同じだが, 独立した変数.

#include <stdio.h>

void func(void)
{
    int a;

    /* 代入が行われるまでは a の値は不定. 呼出側で同じ名前の変数が使わ
    れていても関係がない. */
    a = 3;
    printf("a in func = %d\n", a);

    /* a の値は 呼出側には影響を与えない.*/
}

int main(void)
{
    int a;

    a = 5;
    printf("a in main = %d\n", a);
    func();
    printf("a in main = %d\n", a); /* a = 5 のままであることに注意. */

    return 0;
}

ブロック内でも変数を使用することができるが, その変数はブロック内でのみ有効で, たとえ同じ名前であっても, 他の部分で使われている変数に影響を及ぼさないし, 及ぼされない. 次の例では main 内で宣言されている変数 a とは別に 「{」 と 「}」 で囲まれたブロック内で変数a を使用している. 変数名は同じであるが, これらは互いに影響を及ぼさない変数である. ブロックを出た後では, ブロックに入る前に代入されていた値が残っている. ブロック内で宣言された変数は, ブロック外では使うことはできない.

#include <stdio.h>

int main(void)
{
    int a;

    a = 5;
    printf("a in main = %d\n", a);

    {
        int a;

        a = 3;
        printf("a in func = %d\n", a);
    }

    printf("a in main = %d\n", a); /* a = 5 のまま. */

    return 0;
}
このように, 文法上は同じ名前の変数を独立して使うことができるが, 紛らわしいので名前は変えたほうがいい.

関数の引数として使われている変数は, その関数内に遮蔽されている. void func(int a) と関数を宣言している部分で使われている引数 int a は, func 関数内でのみ使用できる. main 内で使われている a とは独立している.

#include <stdio.h>

void func(int a)
{

    printf("a in func = %d\n", a);
    a = 3;
    printf("a in func = %d\n", a);
}

int main(void)
{
    int a;

    a = 5;
    printf("a in main = %d\n", a);
    func(a);
    printf("a in main = %d\n", a);

    return 0;
}

main 中で func を呼び出している所では a を引数に 渡している. 関数呼び出しでは, 引数を受け取るための領域が新たに用意され, 変数として利用される. 呼び出しが行われる時に, 各引数への代入が行われる. ここでは, func 関数を呼び出す時に func 側の引数 a の領域が確保され, main 側の a の値すなわち5 が, そこに代入される. その後関数本体の実行に 移る.

引数のために用意されている変数は,関数内で使用されている変数と同じよう に, 変数として使うことができる. ここでは func 関数内で a=3 を実行しているが,このように引数に使われている変数に代入を行 うこともできる.

ここまでで明らかになったように, 関数内やブロック内で宣言された変数は, その関数やブロックでのみ使用でき, たとえ他のところで同じ名前の変数が使 われていても, それらとは全く独立に使用できる. これにより, 他の部分でど のような変数が使われているか, 又それらはいつ変更されるかということを全 く考慮せずに, 関数やブロックをプログラムすることが可能になる. これは少 し大きめなプログラムを書こうとしたら, すぐに有用となる C 言語の重要な 機能のうちの一つである.

再度具体的な例を挙げる.

練習問題を挙げておく. 以下のプログラムにおいて, (1)から(13)までの各printfによりaの値を出力するとき, それぞれ [A]から[G]のうちどのaの値が出力されるか答えよ.

#include <stdio.h>

double a = 10.0; /* [A] */

void func1(double a) /* [B] */
{
	 printf("a = %g\n", a); /* (1) */

	 {
		  double a = 10.0; /* [C] */
		  printf("a = %g\n", a); /* (2) */
	 }

	 printf("a = %g\n", a); /* (3) */
}

void func2(void)
{
	 printf("a = %g\n", a); /* (4) */
	 {
		  double a = 10.0; /* [D] */
		  printf("a = %g\n", a); /* (5) */
	 }
	 printf("a = %g\n", a); /* (6) */
}

void func3(double x)
{
	 printf("a = %g\n", a); /* (7) */
	 {
		  double a = 10.0; /* [E] */
		  printf("a = %g\n", a); /* (8) */
	 }
	 printf("a = %g\n", a); /* (9) */
}

int main(void)
{
	 double a = 10.0; /* [F] */
	 printf("a = %g\n", a); /* (10) */
	 func1(a);
	 func2();
	 func3(a);
	 printf("a = %g\n", a); /* (11) */
	 {
		  double a = 10.0; /* [G] */
		  printf("a = %g\n", a); /* (12) */
	 }
	 printf("a = %g\n", a); /* (13) */

	 return 0;
}

次のように, 独立な変数には別な名前を付け, 異なる値を代入しておけば, どの変数が用いられているかわかる.

#include <stdio.h>

double a = 10.0; /* [A] */

void func1(double b) /* [B] */
{
	 printf("(01) b = %g\n", b); /* (1) */

	 {
		  double c = 20.0; /* [C] */
		  printf("(02) c = %g\n", c); /* (2) */
	 }

	 printf("(03) b = %g\n", b); /* (3) */
}

void func2(void)
{
	 printf("(04) a = %g\n", a); /* (4) */
	 {
		  double d = 30.0; /* [D] */
		  printf("(05) d = %g\n", d); /* (5) */
	 }
	 printf("(06) a = %g\n", a); /* (6) */
}

void func3(double x)
{
	 printf("(07) a = %g\n", a); /* (7) */
	 {
		  double e = 40.0; /* [E] */
		  printf("(08) e = %g\n", e); /* (8) */
	 }
	 printf("(09) a = %g\n", a); /* (9) */
}

int main(void)
{
	 double f = 50.0; /* [F] */
	 printf("(10) f = %g\n", f); /* (10) */
	 func1(f);
	 func2();
	 func3(f);
	 printf("(11) f = %g\n", f); /* (11) */
	 {
		  double g = 60.0; /* [G] */
		  printf("(12) g = %g\n", g); /* (12) */
	 }
	 printf("(13) a = %g\n", a); /* (13) */

	 return 0;
}

関数の仮引数は, auto変数であり, 関数呼び出しの際に仮引数に実引数の値が代入されていることに注意.

20.3.3.3 auto変数とスタック

auto変数 はスタック [20.12.2] と呼ばれる領域に値を保持する場所が確 保されるが, この領域は十分に広くないかも知れない. 大きな配列を auto 変 数として宣言するとこの領域に入りきらずその関数を呼び出した時点で stack overflow となって異常終了してしまうことがある.
/* この例では, stack overflow を起こそうとしているが, 実際にはなかなか
起きない. もっと大きな配列を確保しなければならないかも知れない. */

#include <stdio.h>

void func(void)
{
    int a[10000];

    int i;

    for(i=0; i<10000; i++)
        a[i] = i;

    printf("a in func = %d\n", a[0]);
}

int main(void)
{

    printf("in main.\n");
    func();
    printf("in main again.\n");

    return 0;
}

stack overflow が起きる時には, この配列の使用時間が十分に長いなら static な配列として宣言してもいいかも知れない. ただし, 本来は malloc 関数等を使ってメモリー領域を動的に確保するのが正しいやり方である.

/* static 変数は, stack 領域とは別の所から領域を確保するだろうから, う
まくいくかも知れない. */

#include <stdio.h>

void func(void)
{
    static int a[10000]; /* <- 注目. */

    int i;

    for(i=0; i<10000; i++)
        a[i] = i;

    printf("a in func = %d\n", a[0]);
}

int main(void)
{

    printf("in main.\n");
    func();
    printf("in main again.\n");

    return 0;
}

20.3.3.4 static 変数

ここまでで扱った変数は, 関数やブロックが実行されている間だけ存在した. 複数回呼び出される場合には, 前回の呼び出しが行われた時の値は保持してい ない. このような変数は auto 変数と呼ばれる. これに対して, 変数が永続的 に存在して前回の値を保持している変数も時として必要となる. このような変 数は static というキーワードを用いて宣言する. auto 変数は関数呼び出し が行わる時に「動的」に領域が確保される. これに対し「static」なのである. static 変数の場合, プログラムの実行が始まってから終了するまでの間, 同 じ領域がその変数のために存在することが保証されている.

次の例では, func 関数内でのみで使える static 変数 a を利 用している. 宣言は auto 変数と同様に行い前に static を付ける. 最初の 呼び出しの時には値が確定していて欲しい時には, 下の例のような代入を宣言 時に行っておく. これにより最初に参照されるときにはその値が代入されて いる. この例では,関数が呼び出されるごとに 1 だけ値が加えられて いく.

#include <stdio.h>

void func(void)
{
    static int a = 3;

    a++;
    printf("a = %d\n", a);
}

int main(void)
{
    func();
    func();
    func();
    func();

    return 0;
}

もう一つ例を挙げておく.

/*
  static 変数
  ==========
  static を付けて変数を宣言する。
  寿命は, プログラム開始から終了までの間となる。
  初期化は, プログラム開始時に1度だけ行われる。

  関数内で宣言されていても、前回その関数を呼出した時の値が保持されていて
  その値を用いることができる。
  
  自動変数 (auto変数)
  ================
  static を付けずに変数を宣言する。
  寿命は、変数が宣言されてから、その変数のブロック(スコープ)が終了するまで。
  関数の先頭で宣言されている場合は、関数から戻るまでになる。
  初期化は、変数が宣言されるたびに毎回行われる。

  関数から戻るときに、変数の値を保持する為の領域がなくなるので、
  前回その関数を呼出したときの値は保持されない。
*/
 

#include <stdio.h>

// funcA ではstatic を付けて int aが宣言されている。
// プログラム開始時のみ, aが1に初期化され、
// 呼出されるたびにaの値が2倍になり、その値が記憶される。

int funcA(void)
{
  static int a = 1;
  a *= 2;
  return a;
}

// funcB ではstatic を付けないで int aが宣言されている。
// 関数が呼び出され、変数を宣言するたびに, bが1に初期化される。
// 次回の呼び出し時まで値が保持されることはない。

int funcB(void)
{
  int b = 1;
  b *= 2;
  return b;
}

int main(void)
{
  int i, val;

  for(i=0; i<10; i++){
    val = funcA();
    printf("%d\n", val);
  }

  for(i=0; i<10; i++){
    val = funcB();
    printf("%d\n", val);
  }
}

20.3.3.5 大域変数

ここまででは, 関数やブロックの内部で宣言された変数はその関数やブロックの内部でのみ参照可能であることを説明してきた. 逆に値を変更したい場合もある.

大域変数は, 次のように関数の外部で変数を宣言することにより利用できる. 初期値が必要なときには, 宣言に続けて代入を行っておく. この変数は, プロ グラム全体を通じて, 1つしか存在せず, どの関数からでも読み書きできる. どこからでも読み書きができ, プログラムの保守性が悪くなるので, 大域変数 は極力使わないほうがいい.

#include <stdio.h>

int a = 5;

void func(void)
{
    printf("a in func = %d\n", a);
    a = a + 10;
    printf("a in func = %d\n", a);
}

int main(void)
{
    a = a + 10;
    printf("a in main = %d\n", a);
    func();
    printf("a in main = %d\n", a);
    a = a + 10;
    printf("a in main = %d\n", a);

    return 0;
}

20.3.3.6 extern による翻訳単位外の変数の参照

20.3.3.7 static による翻訳単位外からの変数の遮蔽

C 言語のプログラムは複数のファイル (翻訳単位) に分割することが可能であ るが, その翻訳単位の中だけで大域変数を読み書きし, 他の翻訳単位からは遮 蔽することもできる. そのためには, static int a; のように大域変 数を宣言するときに前に static を付ける.

20.3.4 配列

二次元配列.

20.3.4.1 関数の仮引数としてのポインタの宣言

#include <stdio.h>
#include <stdlib.h>

/*
  関数の仮引数としての配列.
  keyword: 配列, ポインタ
 */


// 関数の仮引数の x はポインタ.
void funcA(int x[], int n)
{
	 int i;

	 for(i=0; i<n; i++)
		  printf("%d\n", x[i]);

	 for(i=0; i<n; i++)
		  printf("%d\n", *x++);	 // *xの値も変更できるし, xの値を変更できる.
}

int main(void)
{
	 int i;
	 int a[] = {1, 2, 3}; // aは配列名.
	 int n = sizeof(a)/sizeof(a[0]); // a の要素数.
	 
	 funcA(a, n);

	 for(i=0; i<n; i++)
		  printf("%d\n", a[i]);

	 *a= 10;                     // *a の値は変更できる.
	 *(a+1) = 20;
	 *(a+2) = 30;

	 for(i=0; i<n; i++)
		  printf("%d\n", a[i]);

// #if 0 としておくことで常に偽なので, 以下の部分はコンパイル時に省かれる.
// ------- ここから ------------------------------------------------
#if 0
	 for(i=0; i<n; i++)
		  printf("%d\n", *a++); // a の値は変更できない.
#endif
// ------- ここまで -------------------------------------------------

	 return EXIT_SUCCESS;
}

大きな配列を操作する場合に, 全ての配列要素の値を関数に渡し, 全ての配列要素の値を返すのは効率が悪い. この場合, 配列の実体は一度だけ用意し, それを操作するのがよい.

次に, 配列を引数とする関数を示す. 配列名を引数とする場合, 配列のアドレスを関数に渡すことになる.

#include <stdio.h>

#define NUMBER 5

/* 配列の各要素に100を加える関数 */
void func(double a[NUMBER])
{
	 int i;

	 for(i=0; i<NUMBER; i++)
		  a[i] += 100.0;
}

int main(void)
{
	 int i;
	 double x[NUMBER] = {0.0}; // 全ての要素を 0.0 に初期化.

	 for(i=0; i<NUMBER; i++)
		  printf("x[%d] = %g\n", i, x[i]);

	 func(x);

	 for(i=0; i<NUMBER; i++)
		  printf("x[%d] = %g\n", i, x[i]);
	 
	 return 0;
}

  1. main関数で, double x[NUMBER]; を宣言し, func(x);のように関数funcを呼び出している. この時xは, 配列x[]の先頭のアドレスを表している. したがって, 関数funcに配列x[]の先頭のアドレスを渡していることになる. 関数funcに配列の全ての要素の値を渡しているわけではない.

  2. 関数funcは, void func(double a[NUMBER])と宣言されている. ここで, 仮引数double a[NUMBER]aはポインター変数でアドレスを受け取り, a に main 関数中の配列 int x[NUMBER]の先頭のアドレスが代入される. ここで, double a[NUMBER]という double型で NUMBER個の配列の値を保持するための領域は, 新たに確保されないことに注意する必要がある.
  3. 関数func内では, a[i]に代入を行っている. aはアドレスを保持するポインタ変数で, 添字演算子[]を付けることで, aが指すアドレスから i進んだアドレスの値を意味する. これにより, 値の変更や参照を行う. ここでは, 配列x[]の値が変更される.


20.3.5 可変長配列

C99では, ブロックの中または関数引数の中で, 可変長配列を用いることができる.

20.3.5.1 関数の引数としての可変長配列

関数に渡す配列の大きさを, 関数に渡す引数で与えられる.
// 以下は C99 のプログラムです.

#include <stdio.h>

// 配列の大きさを関数宣言で決められる.
void func(int m, int data[m])
{
	 int i;

	 for(i=0; i<m; i++)
		  data[i] *= 10;
}

int main(void)
{
	 int n, i;
	 n = 10;

	 int x[n]; // 配列のサイズを, プログラム実行時に与えられる.

	 for(i=0; i<n; i++)
		  x[i] = i;

	 func(n, x);
	 
	 for(i=0; i<n; i++)
		  printf("%d\n", x[i]);

	 return 0;
}

ただし, 配列dataの大きさを決定する時点で, 大きさmが決まっている必要がある. 関数宣言は前から順に評価されるので, 上の例では mの値に基づき int data[m]が宣言される.

この順を逆にして

void func(int data[m], int m)
{
    ...
}
のように宣言することはできない. int data[m]を宣言する時点で, int mはまだ宣言されておらず, 大きさが決められない.

そこで,

void func(data, m)
    int m;
    int data[m];
{
    ....
}
のように 引数 int data[m], int m の型宣言を 関数の宣言 void func(data, m)の後に行う. この場合, 関数の引数の並び順は void func(data, m)の部分で決まり, 引数の型は, それ以降の int m, int data[m] の部分で決まる. また, 引数の評価は前から順に行われる. この例では, int mの宣言が行われた後, int data[m] の宣言が行われる.

このような関数では, プロトタイプ宣言を

void func(int data[m], int m);
のにうよすることはできない. ここでも, mがどのmのことであるかが指定されていなければならない. しかし, 前から順に評価することになっているので, int data[m]の時点では, mが定義されていないことになり, エラーとなる.

そこで

void func(int data[*], int m);
のように*を用いたプロトタイプ宣言をする方法が用意されている.

従って, func(data, m) の順に引数を並べたい場合は, 次のようになる.

// C99.

#include <stdio.h>

// 関数の引数を int data[m], int m の順にする方法.

// この例では, このプロトタイプ宣言はなくてもよい.
// プロトタイプ宣言をするとしたら次のようになる.
void func(int data[*], int m); // プロトタイプ宣言では, * を用いる.

void func(data, m) // ここでは, 引数の型は宣言しない.
     int m;        // その後引数の宣言をする. 引数は前から順に評価される.
     int data[m];  // この時点で m がどのmか分かっている.
{
	 int i;

	 for(i=0; i<m; i++)
		  data[i] *= 10;
}

int main(void)
{
	 int n, i;
	 n = 10;

	 int x[n]; // 配列のサイズを, プログラム実行時に与えられる.

	 for(i=0; i<n; i++)
		  x[i] = i;

	 func(x, n);
	 
	 for(i=0; i<n; i++)
		  printf("%d\n", x[i]);

	 return 0;
}

int data[m] が実行される時点で, mが宣言されていればよいので次のようなプログラムも動作する.

// C99.

#include <stdio.h>

// data[m] の宣言を行う際に, 予め m が宣言されていればよい.
int m;

void func(int data[m])
{
	 int i;

	 for(i=0; i<m; i++)
		  data[i] *= 10;
}

int main(void)
{
	 int n, i;
	 n = 10;

	 int x[n]; // 配列のサイズを, プログラム実行時に与えられる.

	 for(i=0; i<n; i++)
		  x[i] = i;

	 m = n;
	 func(x);
	 
	 for(i=0; i<n; i++)
		  printf("%d\n", x[i]);

	 return 0;
}
ただし, これはあくまでも可変長配列を関数の引数にすることを理解するためのプログラムであり, 実用上は好ましくない. 変数を大域変数にしてしまっているので, プログラムの保守性が悪くなっているからである.

2次元以上の配列でも, 関数の引数にする配列の大きさが, 引数で与えられる.

// 以下は C99 のプログラムです.

#include <stdio.h>

// プロトタイプ宣言をする場合は, 次のいずれか.
// 可変でない配列と同じ事情で, data に一番近い次元の部分は省略できる. 
// void func(int p, int q, int data[p][q]);
// void func(int p, int q, int data[][q]);
// void func(int p, int q, int data[*][*]);
// void func(int p, int q, int data[][*]);

void func(int p, int q, int data[p][q]) // 配列の大きさを関数宣言で決められる.
{
	 int i, j;

	 for(i=0; i<p; i++)
		  for(j=0; j<q; j++)
			   data[i][j] = i*10 + j;
}

int main(void)
{
	 int m, n, i, j;
	 m = 3;
	 n = 4;

	 int x[m][n]; // 配列のサイズを, プログラム実行時に与えられる.

	 func(m, n, x);
	 
	 for(i=0; i<m; i++)
		  for(j=0; j<n; j++)
			   printf("%d\n", x[i][j]);

	 return 0;
}

20.3.5.2 pointer

int m, n;

m = 3;
n = 5;

int data[m][n];
int (*ptr)[m][n];

ptr = data;

20.3.5.3 typedef

typedef int array[n];

typedef の時点での n の値が用いられる. その後 n の値を変更しても, array のサイズは変化しない.

20.3.5.4 sizeof

実行時に, 大きさが評価される.

20.3.6 文字列

(ドラフト)

文字列: 文字の配列. 文字列の最後には '\0' で表す.

演算は定義されていない. 代わりに関数が用意されている.

ポインター char *ptr; "abc ABC" はポインター

char str[]; は配列. char str[] = ...; は配列の初期化.

文字配列の初期化は次のようにして行なうことができる.

char str[] = "aaa bbb ccc eee";
ここで str は配列で 初期化する文字列と同じだけの大きさを持つ.

一方

char *str = "aaa bbb ccc eee";
と宣言したときの str は文字列へのポインターである. ここでは "aaa bbb ccc eee" がそれ自身へのアドレスを返しており, そのアドレスをポインターstr に代入している. このままでは *str への代入は行なえないことに注意する必要がある.

"aaa bbb ccc eee" が文字列定数へのポインターを返すことから "aaa bbb ccc eee"[4] のような表現が可能である. この例では, 前か らのオフセットが4 すなわち bbb の部分の最初の b の意味に なる. 次のような用法は時として有用である.

#include <stdio.h>

int main(void)
{
    int i;
    char c;

    for(i=0; i<10; i++){
        c = "abcdefg"[i];
        printf("%c", c);
    }
    return 0;
}

#include <stdio.h>

int main(void)
{
    int c;

    while( (c=getchar()) != EOF )
        putchar(c);

    return 0;
}


20.3.7 struct: 構造体

20.3.7.1 変数のグループ化

構造体を用いると幾つかの変数をグループ化し扱うことができる. 構造体の構成要素(メンバー)には C 言語で許されている変数であれば何でも よい.

まず struct { ... }; によりどのような要素を持つ構造体か定義しそ れに名前を付ける. 次の例では, double 型の変数 x と y を要素に持つpoint という名前の構造体を定義している. このようにして新たなデータ型を導入す ることができる. 構造体は, 他の変数と同様に任意の場所で, 任意の数だけ使 用できる. ここでは, main 関数の中で a という名前で 1 つだけ使用してい る. 構造体のメンバーを参照するには「.」を使う. 変数名の後に「.」を付け, その後にメンバー名を書くことでそのメンバーを参照したり代入したりできる. ここの例では, a という名前の構造体の x というメンバーを a.x と表現して いる. これは, x というメンバーは double 型で宣言しているので, a.x は double型の変数と同様に扱うことができる.

#include <stdio.h>

struct point {
    double x;
    double y;
};

int main(void)
{
    struct point a;

    a.x = 10.0;
    a.y = 15.0;

    printf("(%g, %g)\n ", a.x, a.y);

    return 0;
}

構造体同士は代入を行なうことができる.

#include <stdio.h>

struct point {
    double x;
    double y;
};

int main(void)
{
    struct point a, b;

    a.x = 10.0;
    a.y = 15.0;

    b = a;
    printf("(%g, %g)\n ", b.x, b.y);

    return 0;
}

20.3.7.2 関数への引数

次に, 関数に構造体を渡す例を示す.
#include <stdio.h>

struct point {
    double x;
    double y;
};

void print(struct point a)
{
    printf("(%g %g)\n", a.x, a.y);
}

int main(void)
{
    struct point a;

    a.x = 10.0;
    a.y = 15.0;

    print(a);

    return 0;
}

上の例では, Call by Value による関数呼び出しを行なったが, Call by Reference もできる.

#include <stdio.h>

struct point {
    double x;
    double y;
};

void print(struct point *a)
{
    printf("(%g %g)\n", (*a).x, (*a).y);
}

int main(void)
{
    struct point a;

    a.x = 10.0;
    a.y = 15.0;

    print(&a);

    return 0;
}
ポインター変数で構造体のアドレスを受けっとった時に, メンバーの値の参照 は, *a.x ではなく (*a).x であることに注意. 「*」 より「.」 の方が優先順位が高いので *a.x は, *(a.x) の意味に解釈される. 同等な意味をもつ表記法が別に用意され ている. -> を用いて, (*a).xa->x と書くことも できる. 通常は後者を用いた方が分かりやすいかも知れない.

Call by Value の時には, 引数のためのに変数領域が確保され, まずそこへの 代入が行なわれた後に, 関数内部の実行が行なわれる. この引数として使われ ている(構造体の各メンバーの)変数の値を変更して利用することはできる. ただし, 関数から戻ったときには, 引数として渡した構造体の各メンバーの値 は関数呼び出しを行なう前と全く同じである.

Call by Reference の時には, 構造体の実体が新たに作成されることはない. 引数として渡す構造体がそのまま利用される. その結果, 呼び出した関数内部 で値の変更を行なった場合には, 呼び出した側の構造体のメンバーの変数の値 も変更される.

Call by Value は, 関数の構造化に有用であるが, 関数呼び出しを行なうたび に, 新たに構造体の実体が用意されそこへの代入が行なわれるので, 効率が良 くはない. 一方, Call by Reference は, 呼び出す側の変数を変更してしまう ので関数の構造化に適していないが, 効率が良い.

20.3.7.3 関数からの戻り値

又, 関数が戻り値として構造体を返すこともできる.
#include <stdio.h>

struct point {
    double x;
    double y;
};

struct point scale(struct point p)
{
    struct point ret;

    ret.x = p.x * 100.0;
    ret.y = p.y * 100.0;

    return ret;
}

int main(void)
{
    struct point a, b;

    a.x = 10.0;
    a.y = 15.0;

    b = scale(a);

    printf("b: x=%g, y=%g\n", b.x, b.y);

    return 0;
}

20.3.7.4 構造体に配列を入れる

C 言語は, 配列に対する次の操作はできない.

しかし, 配列を構造体のメンバーにすれば結果的に可能となる. 次にこれら について順に説明する.

代入は次のように行なえる.

#include <stdio.h>

struct points {
    double x[10];
};

int main(void)
{
    int i;
    struct points a, b;
    
    for(i=0; i<10; i++)
        a.x[i] = i;

    b = a;
    for(i=0; i<10; i++)
        printf("b.x[%d] = %g\n", i, b.x[i]);

    return 0;
}

構造体のメンバーに配列を含んだ場合, 構造体を引数にとる関数は次のように なる. まずは, Call by Value の例を示す.

#include <stdio.h>

struct points {
    double x[10];
    double y[10];
};

void report(struct points p)
{
    int i;

    for(i=0; i<10; i++)
        printf("%d: (%g, %g)\n", i, p.x[i], p.y[i]);
}

int main(void)
{
    int i;
    struct points a;
    
    for(i=0; i<10; i++){
        a.x[i] = i;
        a.y[i] = i * 10.0;
    }

    report(a);

    return 0;
}

次に Call by Reference の例を示す.

#include <stdio.h>

struct points {
    double x[10];
    double y[10];
};

void report(struct points *p)
{
    int i;

    for(i=0; i<10; i++)
        printf("%d: (%g, %g)\n", i, (*p).x[i], (*p).y[i]);
}

int main(void)
{
    int i;
    struct points a;
    
    for(i=0; i<10; i++){
        a.x[i] = i;
        a.y[i] = i * 10.0;
    }

    report(&a);

    return 0;
}

関数の戻り値として構造体を返すことができる.

#include <stdio.h>

struct points {
    double x[10];
};

struct points addoffset(struct points p)
{
    struct points ret;
    int i;

    for(i=0; i<10; i++)
        ret.x[i] = p.x[i] + 100.0;

    return ret;
}

int main(void)
{
    int i;
    struct points a, b;
    
    for(i=0; i<10; i++)
        a.x[i] = i;

    b = addoffset(a);
    for(i=0; i<10; i++)
        printf("b.x[%d] = %g\n", i, b.x[i]);

    return 0;
}

20.3.7.5 ポインター変数を含む構造体

構造体のメンバーにポインター変数を含めることができるが, そのポインター が指す変数や配列を別に確保する必要があることに注意する必要がある.

まずは, 変数の場合について説明する.

#include <stdio.h>

struct point {
    double *x;
};

int main(void)
{
    double value;
    struct point a;

/* 次のはエラー. a.x が指す対象が確定していない.
    *a.x = 10.0;
*/

    /* 次に a.x が value を指すように設定している. 10.0 は value に代
    入されていることに注意. */
    a.x = &value;
    *a.x = 10.0;

    printf("%g\n", *a.x);
    return 0;
}

#include <stdio.h>

struct points {
    double *x;
};

int main(void)
{
    double value[10];
    struct points a;
    int i;

/*  次のはエラー. a.x が指す対象が確定していない.
    for(i=0; i<10; i++)
        a.x[i] = 10.0;
*/
    /* 次に a.x が value を指すように設定している. 10.0 * i は value[i] に代
    入されていることに注意. */
    a.x = value;
    for(i=0; i<10; i++)
        a.x[i] = 10.0 * i;

    for(i=0; i<10; i++)
        printf("%g\n", a.x[i]);
    return 0;
}
関数内部で宣言した配列は, 関数から戻るときに消滅する. 従って関数から戻っ たの値に参照することはできない. この意味において次のプログラムは誤りで ある.
#include <stdio.h>

struct points {
    double *x;
};

struct points addoffset(struct points p)
{
    struct points ret;
    double value[10];
    int i;

    ret.x = value;
    for(i=0; i<10; i++)
        ret.x[i] = p.x[i] + 100.0;

    return ret;
}

int main(void)
{
    double a_value[10];
    struct points a, b;
    int i;

    a.x = a_value;
    for(i=0; i<10; i++)
        a.x[i] = 10.0 * i;

    b = addoffset(a);

    for(i=0; i<10; i++)
        printf("%g\n", b.x[i]);
    return 0;
}
上の例の中の addoffset 関数の中で double value[10]; と宣言して いるところを static 変数にして, static double value[10]; とすれ ば一見うまくいくようである. しかし, プログラム中で static doublevalue[10] の実体は1つしかな いので, あるところでその値を変更すると別の構造体でも変更が起きる. 次の 例では 構造体 c の値は変更していないことを意図しているがそうは なっていない.
#include <stdio.h>

struct points {
    double *x;
};

struct points addoffset(struct points p)
{
    struct points ret;
    static double value[10];
    int i;

    ret.x = value;
    for(i=0; i<10; i++)
        ret.x[i] = p.x[i] + 100.0;

    return ret;
}

int main(void)
{
    double a_value[10];
    struct points a, b, c;
    int i;

    a.x = a_value;
    for(i=0; i<10; i++)
        a.x[i] = 10.0 * i;

    b = addoffset(a);
    for(i=0; i<10; i++)
        printf("b: %g\n", b.x[i]);

    c = addoffset(a);
    /* b.x[3] しか変更していないつもり. */
    b.x[3] = 0.0;
    for(i=0; i<10; i++)
        printf("c: %g\n", c.x[i]);


    return 0;
}

結局, メンバーにポインター変数を持っていて, それが指す内容を各構造体ご とに読み書きしたいときには, 各構造体ごとにそれが指す変数や配列を用意す る必要があることがわかった. 要素数を実行時に決定したい配列をメンバーと したい時がこれにあたる.

次に具体例として, 実行時に要素数を決められる配列を使う例を示す. 構造体 を宣言したらそこで必要な配列の領域を確保する (points_new). その 構造体をもう使わないのであるなら, 消去する (points_delete).

#include <stdio.h>
#include <stdlib.h>

struct points {
    int n;
    double *x;
};

struct points points_new(int num)
{
    struct points p;

    p.n = num;
    p.x = (double *)malloc( num * sizeof(double) );
    if(p.x == NULL){
        perror("points_init:");
        exit(1);
    }

    return p;
}

void points_delete(struct points p)
{
    free(p.x);
}

int main(void)
{
    struct points a;
    int i;

    a = points_new(10);

    for(i=0; i<a.n; i++)
        a.x[i] = 10.0 * i;

    for(i=0; i<a.n; i++)
        printf("a: %g\n", a.x[i]);

    points_delete(a);

    return 0;
}
通常の配列はコンパイル時に要素数を与える必要がある. これに対し, 上の例 では実行時に要素数を与えられている.

関数の戻り値として返すときには次のようになる.

#include <stdio.h>
#include <stdlib.h>

struct points {
    int n;
    double *x;
};

struct points points_new(int num)
{
    struct points p;

    p.n = num;
    p.x = (double *)malloc( num * sizeof(double) );
    if(p.x == NULL){
        perror("points_init:");
        exit(1);
    }

    return p;
}

void points_delete(struct points p)
{
    free(p.x);
}

struct points points_addoffset(struct points p)
{
    struct points ret;
    int i;

    ret = points_new(p.n);
    for(i=0; i<ret.n; i++)
        ret.x[i] = p.x[i] + 100.0;

    return ret;
}

int main(void)
{
    struct points a, b, c;
    int i;

    a = points_new(10);

    for(i=0; i<a.n; i++)
        a.x[i] = 10.0 * i;

    b = points_addoffset(a);
    for(i=0; i<b.n; i++)
        printf("b: %g\n", b.x[i]);

    c = points_addoffset(a);
    /* ここでは, b.x[3] の値を変更しているが, b.x 専用の領域を確保して
    あるので c.x は影響を受けない. */
    b.x[3] = 0.0;
    for(i=0; i<c.n; i++)
        printf("c: %g\n", c.x[i]);

    /* 使わなくなったら直ちに消去する. */
    points_delete(a);
    points_delete(b);
    points_delete(c);

    return 0;
}
しかし, 上のプログラムでは関数呼び出しを行なうたびに 配列のための領域 が確保されるので, 毎回事前に points_delete を呼び出す必要がある. 次のは, main 関数のみ上のプログラムから変更した.
int main(void)
{
    struct points a, b;
    int i;

    a = points_new(10);

    for(i=0; i<a.n; i++)
        a.x[i] = 10.0 * i;

    b = points_addoffset(a);
    for(i=0; i<b.n; i++)
        printf("b1: %g\n", b.x[i]);

    /* ここで a.x に別の値を代入し, ... */
    for(i=0; i<a.n; i++)
        a.x[i] = 20.0 * i;

    /* points_addoffset を再び呼び出したいが, それに先立ち
    points_delete を呼び出す必要があり, 煩雑である. */
    points_delete(b);
    b = points_addoffset(a);
    for(i=0; i<b.n; i++)
        printf("b2: %g\n", b.x[i]);

    points_delete(a);
    points_delete(b);

    return 0;
}

若干危険性が増すが, Call by Reference による次のような実装が現実的かも 知れない. 初期化関数 points_new は, 構造体 points を宣言したら 必ず呼び出す必要がある.

#include <stdio.h>
#include <stdlib.h>

struct points {
    int n;
    double *x;
};

void points_new(struct points *p, int num)
{
    p->n = num;

    p->x = (double *)malloc( num * sizeof(double) );
    if(p->x == NULL){
        perror("points_init:");
        exit(1);
    }
}

void points_delete(struct points *p)
{
    free(p->x);
}

void points_addoffset(struct points *to, struct points *from)
{
    int i;

    if(to->n != from->n){
        fprintf(stderr, "points_addoffset: dimension mismatch.\n");
        exit(1);
    }

    for(i=0; i < from->n; i++)
        to->x[i] = from->x[i] + 100.0;
}

int main(void)
{
    struct points a, b;
    int i;

    points_new(&a, 10);
    points_new(&b, 10);

    for(i=0; i<a.n; i++)
        a.x[i] = 10.0 * i;

    points_addoffset(&b, &a);
    for(i=0; i<b.n; i++)
        printf("b: %g\n", b.x[i]);

    points_delete(&a);
    points_delete(&b);
    return 0;
}

20.3.8 union: 共用体

20.3.9 enum

指定しないときには 0 から順に 0, 1, 2, ... と値が割り振られていく. 次 の例では, KEY_A=0, KEY_B=1, KEY_C=2, KEY_LAST=3 となる. KEY_A, KEY_B, KEY_C を 使用するときに, 最後に KEY_LAST を定義し, これにより項目の数 (3) を得ている.

enum {
    KEY_A,
    KEY_B,
    KEY_C,
    KEY_LAST,
};

int values[KEY_LAST] = { 0 };

typedef enum
{
    FLAG_READABLE = 1 << 0,
    FLAG_WRITABLE = 1 << 1,
    FLAG_SYNC     = 1 << 2,
    FLAG_ASYNC    = 1 << 3,
    /* aliases */
    FLAG_READWRITE  = FLAG_READABLE | FLAG_WRITABLE,
    FLAG_MASK = FLAG_READABLE | FLAG_WRITABLE | FLAG_SYNC | FLAG_ASYNC
} flags;

次の例では, void func(aaa x);enum _aaa { ... }; より 後に記述する必要がある.

#include <stdio.h>

typedef enum _aaa aaa;

enum _aaa {
    aaa_aaa = 1,
    aaa_bbb = 2,
    aaa_ccc = 4,
};

void func(aaa x);

void func(aaa x)
{
    printf("%d\n", x);
}

int main(void)
{
    aaa val;

    val = aaa_bbb;
    
    func(val);
    
    return 0;
}
このようなことは必ずしも保証されないので ヘッダーファイルで
typedef enum {
    aaa_aaa = 1,
    aaa_bbb = 2,
    aaa_ccc = 4,
} aaa;
と定義して, これを取り込むのがいい.

20.3.10 typedef

struct _string
{
    int len;
    char *str;
};

typedef struct _string string;

20.3.11 変数の初期化

20.4 演算


20.4.1 演算子一覧

20.4.1.1 +, -, *, /, %: 符号, 加減乗除

+
+ 符号. 「+a」, 「+3」.

-
- 符号. 「-a」, 「-3」.

+
和.

-
差.

積. (数学で用いられる記号 $\times$ とは異なることに注意.)

/
商. 00.0 で割らないようにチェックしておく必 要がある.

%
余り. 例えば 「10%4」 は 「2」.

20.4.1.2 =: 代入

=」 は右辺の値を左辺に代入することを意味する. 他に, 「代入す る左辺の変数」と「右辺の演算される変数のうちの1つ」が一致する時, 次の ような表記を用いることができる.

演算子 等価な表現
*= x *= a; x = x * a;
/= x /= a; x = x / a;
%= x %= a; x = x % a;
+= x += a; x = x + a;
-= x -= a; x = x - a;
<<= x <<= a; x = x << a;
>>= x >>= a; x = x >> a;
&= x &= a; x = x & a;
^= x ^= a; x = x ^ a;
|= x |= a; x = x | a;

代入が行なわれると その代入式自身は代入した値を持つ. 例:

if( (c=getchar()) != EOF ){
    ...
}


20.4.1.3 ++, - -: 1だけ増減

++, - -: 後置インクリメント
被演算数が 1 だけ増加(++)も しくは減少(--)させられる.

++, - -: 前置インクリメント
i++i-- は値を評価 した後に値が変更されるが  [19.3.3], ++i--i は値を評価する前に値が変更される.
#include <stdio.h>

int main(void)
{
    int i;

    i = 0;

    printf("%d\n", ++i);
    printf("%d\n", ++i);
    printf("%d\n", --i);
    printf("%d\n", --i);

    return 0;
}


20.4.1.4 ビット演算

計算機の内部の最も機械よりの部分では全てが 2 進数で表現されている. 整 数や浮動小数点等のデータやプログラム自体もそうである. データの2進数で 表現された各桁 (ビット) を直接操作する (ビット演算) ことも時として必要 となる. ビット演算は, 例えば次の目的で使われる.

ビット演算をするために下の演算子が用意されている. 与えられた数を 2 進 数で表示する関数を用意した [21.6] ので,その関数を用いて演算 結果の確認を行なうといい.
~ 1 の補数.
<< 右側へのシフト.
>> 左側へのシフト.
& ビットごとの AND
| ビットごとの OR
^ ビットごとの 排他的 OR
論理演算を行なうための演算子 「!」(NOT), 「&&」(AND), 「||」(OR) とは異なるので区別して用いること.

$\sim$
各ビットの反転.
«
2 進数で表したときの各ビットを右側にシフトする. x を 1 ビットシフトした値を得るには, x<<1 とする. 2 ビットの時には x<<2 のようになる. その結果を代入するには y = x << 2; となる. 特に x = x << 2; の場合には, x <<= 2; と表記で きる. 例えば 2 進数で 1011 は 1ビットシフトすると 10110, 2ビットシ フトすると 101100 になる. 1ビットシフトすることは2倍することに相当す る.

»
<< と比べ左側にシフトする点が異なる. こちらは $1/2$ 倍することに相当する.

2つの数(もしくは変数)の各ビットごとに AND をとる. 例えば, x & y のようになる. 代入するときには z = x & y; のよう になる. 特に x = x & y; の時には, x &= y; とできる.

|
& と同様. OR を取るところが異なる. z = x | y;, x |= y; 等の代入ができる.

2つの数 (もしくは変数) のビットごとの 排他的 OR を求める. 2 つの数の対応する桁のビットが 同じときその桁は 0 に異なるとき 1 にな る. すなわち 1^1=0, 1^0=1, 0^1=0, 0^0=0. z = x ^ y;, x ^= y; 等の代入ができる.

ビット演算子は, 例えば, 次のように使える.

int main(void)
{
    int x = 0x10;
    int y = 0x15;
    int z;
    
    z = ~x;     /* 1 の補数 (ビットの反転) */
    z = x << 1; /* 1 ビット右にシフト      */
    z = x << 2; /* 2 ビット右にシフト      */
    z = x >> 3; /* 3 ビット左にシフト      */
    z = x | y;  /* ビットごとの OR         */
    z = x & y;  /* ビットごとの AND        */
    z = x ^ y;  /* ビットごとの排他的 OR. 異なると1 同じだと0 */

    return 0;
}

次に, フラグとして用いるためにビット演算を行なうことについて説明する.

2進数の各桁を表す定数を

enum {FLAG_A=1, FLAG_B=2, FLAG_C=4};
又は
#define FLAG_A 1
#define FLAG_B 2
#define FLAG_C 4
で定義しておく. 列挙 enum の方がプログラムの誤りが少なくなる点, デバッグを行ないやすくなる点で優れている. しかし, #define の方 が馴染みがあるかも知れない.

ここで用いられている数は2の冪乗で表される数であることに注意する必要が ある. すなわち 次のように2進数で 1 つの桁が 1 になっている数である.

 1 = 000001
 2 = 000010
 4 = 000100
 8 = 001000
16 = 010000
32 = 100000
   ...
このような数を用いることである属性があるか無いかをそれぞれの桁で表現し, 複数の属性を1つの変数で保持できるようになる.

FLAG_AFLAG_B の属性が両方ともある場合には int flag = FLAG_A | FLAG_B; となる. FLAG_A の属性しかな いときには int flag = FLAG_A; となる.

flag に予め各属性(ビット)が設定されていて, 他の属性(ビット)は変 更せずにある属性(ビット)だけを変更するには, 次のような演算を行なえば良 い.

/* FLAG_A のビットをオンにする. */
flag |= FLAG_A;

/* FLAG_A と FLAG_B のビットをオンにする. */
flag |= FLAG_A | FLAG_B;

/* FLAG_A のビットをオフにする. */
flag &= ~FLAG_A;

/* FLAG_A と FLAG_B のビットをオフにする. */
flag &= ~(FLAG_A | FLAG_B);

/* FLAG_A と FLAG_B の属性のみを取り出す. (mask) */
flag & (FLAG_A | FLAG_B)

/* FLAG_A のビットだけトグルする. */
flag ^= FLAG_A;

より一般にある属性が ある(1) もしくは 無い(0) という2つの種類以上に多 くの状態を取り得る場合には, 複数のビットを用いてその属性を表現すればい い. 例えば 次のように 16進数で 0x1 の桁を 属性A のために, 0x10 と 0x100 の桁を 属性B のために, 0x1000 の桁を属性C のために用いることにす ると次のようになる.

#define FLAG_A_a 0x1
#define FLAG_A_b 0x2
#define FLAG_A_c 0x3

#define FLAG_B_a 0x10
#define FLAG_B_b 0x20
#define FLAG_B_c 0x30
#define FLAG_B_d 0x40
#define FLAG_B_e 0x50
#define FLAG_B_f 0x60
#define FLAG_B_g 0x70
#define FLAG_B_h 0x80
#define FLAG_B_i 0x90
#define FLAG_B_j 0xa0
#define FLAG_B_k 0xb0
#define FLAG_B_l 0xc0
#define FLAG_B_m 0xd0
#define FLAG_B_n 0xe0
#define FLAG_B_o 0xf0
#define FLAG_B_p 0x100
#define FLAG_B_q 0x110
#define FLAG_B_r 0x120

#define FLAG_C_a 0x1000
#define FLAG_C_b 0x2000
#define FLAG_C_c 0x3000

#define FLAG_A_MASK 0xf
#define FLAG_B_MASK 0xff0
#define FLAG_C_MASK 0xf000

/* -------------------------------- */

次に, フラグを使って関数の動作を変更するプログラムの例を示す. draw_bar 関数は与えられた長さ length だけの棒を描く. この時 flag に与えられた種類の太さで描く. 太さとしては BAR_THICK, BAR_MEDIUM, BAR_THIN のいずれかを指定 する. もしくは標準的な太さとしての BAR_DEFAULT を指定することも できる.

flag に指定できる各太さは 2 の冪乗で表せる数を用いていることに注意. 2 進数で表すと

BAR_THICK          001
BAR_MEDIUM         010
BAR_THIN           100

BAR_THICKNESS_MASK 111
となっている. このようにしておくと, 2進で表現した時の各ビットが, ある 動作をするかしないかを指定できるようになる. (ただし, ここでは余り意味 がない.)

#include <stdio.h>

#define BAR_THICK      1
#define BAR_MEDIUM     2
#define BAR_THIN       4

#define BAR_DEFAULT   BAR_MEDIUM

#define BAR_THICKNESS_MASK (BAR_THICK | BAR_MEDIUM | BAR_THIN)

void draw_bar(int length, int flag)
{
    int i, j, thickness;
    char bar;

    bar = '#';

    switch(flag & BAR_THICKNESS_MASK){
    case BAR_THICK:
        thickness = 4;
        break;
    case BAR_MEDIUM:
        thickness = 3;
        break;
    case BAR_THIN:
        thickness = 2;
        break;
    default:
        thickness = 3;
        break;
    }

    printf("\n");
    for(j=0; j<thickness; j++){
        for(i=0; i<length; i++)
            printf("%c", bar);
        printf("\n");
    }
    printf("\n");
}

int main(void)
{
    draw_bar(40, BAR_DEFAULT);
    draw_bar(20, BAR_THICK);
    return 0;
}

上の例のような用途だけなら flag を導入した効果は余り無いかも知 れない. 更に draw_bar 関数に機能を追加しようとした時に効果が現 れる. 上の例では, 太さだけを変えることができた. 更に描画する際の「濃さ」 を変えるようにしたのが下の例である. 引数の数や型を変えずに機能を追加で きているところに注目して欲しい. flag を用いずそれぞれ別の引数と して draw_bar 関数が受け取ることになるとすると, 引数の数が変わっ てしまう. これまでに draw_bar 関数を使うプログラムを多く書いて きた場合には, それを全て書き換える必要が生じる.

各オプションは2進数で表現すると次のようになっている. ここでは, BAR_THICK, BAR_MEDIUM, BAR_THIN の組と BAR_DARK, BAR_LIGHT の組が異なるビットで表現されていることが重要である. 「濃さ」を指定したつもりなのに, 「太さ」が変わってしまっては困る. 両者 が独立に指定できるためには, 異なる桁を使って両者が表現されている必要が ある.

BAR_THICK          00001
BAR_MEDIUM         00010
BAR_THIN           00100

BAR_DARK           01000
BAR_LIGHT          10000

BAR_DARKNESS_MASK  00111
BAR_THICKNESS_MASK 11000

若干無駄だしマスクがうまく作れないが, 10進数でも同じことはできる.

BAR_THICK          1
BAR_MEDIUM         2
BAR_THIN           3

BAR_DARK           10
BAR_LIGHT          20

BAR_DARKNESS_MASK  ???
BAR_THICKNESS_MASK ???
10 の桁が「濃さ」を表し, 1 の桁が 「太さ」を表す.

さて, 新たに 「濃さ」を指定できるようにした draw_bar 関数は次の ようになる.

#include <stdio.h>


#define BAR_THICK     1
#define BAR_MEDIUM    2
#define BAR_THIN      4

#define BAR_DARK      8
#define BAR_LIGHT    16

#define BAR_DEFAULT     (BAR_DARK | BAR_MEDIUM)

#define BAR_DARKNESS_MASK (BAR_DARK | BAR_LIGHT)
#define BAR_THICKNESS_MASK (BAR_THICK | BAR_MEDIUM | BAR_THIN)

void draw_bar(int length, int flag)
{
    int i, j, thickness;
    char bar;

    switch(flag & BAR_DARKNESS_MASK){
    case BAR_DARK:
        bar = '#';
        break;
    case BAR_LIGHT:
        bar = '+';
        break;
    default:
        bar = '+';
        break;
    }

    switch(flag & BAR_THICKNESS_MASK){
    case BAR_THICK:
        thickness = 4;
        break;
    case BAR_MEDIUM:
        thickness = 3;
        break;
    case BAR_THIN:
        thickness = 2;
        break;
    default:
        thickness = 3;
        break;
    }

    printf("\n");
    for(j=0; j<thickness; j++){
        for(i=0; i<length; i++)
            printf("%c", bar);
        printf("\n");
    }
    printf("\n");
}

int main(void)
{
    draw_bar(40, BAR_DEFAULT);
    draw_bar(20, BAR_THICK | BAR_DARK);
    return 0;
}


20.4.1.5 関係

これらの結果は 真 (TRUE) の時 int 型の「1」, 偽 (FALSE) の時 int 型の 「0」になる.

関係演算子
演算子 意味
< 小さい a < b
> 大きい a > b
<= 小さいか等しい a <= b
>= 大きいか等しい a >= b

等値演算子
演算子 意味
== 等しい a == b
!= 等しくない a != b


20.4.1.6 論理演算

if, while, for 等で条件を記述する部分では, 論理式の否定や 論理式同士の 論理和 及び 論理積 により, 複数の論理式の組み合わせることができる.

! 論理否定
&& 論理的 AND
|| 論理的 OR

#include <stdio.h>

int main(void)
{
    int a, b, c;

    a = -3;
    b = 4;
    c = 5;

    if( (a=3) && (b=4) ){
        /* a=3 かつ b=4 の時 */
        printf("(a=3) && (b=4)\n");
    }

    if( (a=3) && (b=4) && (c=5) ){
        /* a=3 かつ b=4 かつ c=5 の時 */
        printf("(a=3) && (b=4) && (c=5)\n");
    }

    if( (a=3) || (b=4) ){
        /* a=3 または b=4 の時 */
        printf("(a=3) || (b=4)\n");
    }

    if( ! ((a=3) || (b=4)) ){
        /* 「a=3 または b=4」 でない時 */
        printf("! ((a=3) || (b=4))\n");
    }

    return 0;
}

論理演算同士の演算順序を制御するには 「(」と「)」 を用いる. 次の例では, まず (c==d) || (e==f) が評価され, 次にその結果と (a==b) の間の演算 && が実行される.

左から右の式へと順に評価され, 条件が満たされることが判明するまで 評価される.

例1
if( (a==b) || (c==d) || (e==f) ) { ... }
例2
if( (a==b) && (c==d) && (e==f) ) { ... }

例1 では, 左から順に評価され1つでも真である式が見つかったら, それ以降の評価を行なわない. 例えば a と b が等しくなく, c と d が等し いときには, (a==b)(c==d) が成立するかどうかは確認さ れるが, (e==f) が成立するかどうかは評価されない.

例2では, 左から順に評価され 1 つでも 偽 である式が見つかったらそれ以降 の評価は行なわれない.

論理式でなく, 数も論理演算に用いることができる. if(n){ }

20.4.1.7 アドレス

&: アドレス演算子
& は被演算数のアドレスを求めるのに使 われる.

*: 間接演算子
* は間接指定を表す. その被演算数が指すオ ブジェクトもしくは関数を返す.


20.4.1.8 sizeof 演算子

オブジェクトのサイズを知るには sizeof 演算子を用いる.

#include <stdio.h>

int main(void)
{
    printf("char        : %d\n", sizeof(char));
    printf("short int   : %d\n", sizeof(short));
    printf("int         : %d\n", sizeof(int));
    printf("long int    : %d\n", sizeof(long));
    printf("float       : %d\n", sizeof(float));
    printf("double      : %d\n", sizeof(double));
    printf("long double : %d\n", sizeof(long double));

    printf("char *      : %d\n", sizeof(char *));
    printf("double *    : %d\n", sizeof(double *));

    return 0;
}

初期化された配列の要素数は次のようにして得られる.

#include <stdio.h>

int main(void)
{
    double data[] = {1.0, 2.0, 3.0, 4.0};
    double *ptr;


    /* sizeof(data) は data 全体の大きさを返す.               */
    /* sizeof(data[0]) は配列要素 1 つ当りの大きさに相当する. */
    /* 従って次のようにして 配列の要素数を得ることができる.   */

    printf("number = %d\n", sizeof(data) / sizeof( data[0] ));

    ptr = data;

    /* sizeof(ptr) は ptr がポインターなのでポインタ自身の大きさを返す. */
    /* sizeof(*ptr) は *ptr は double 型の数なので */
    /* sizeof(double) を返す. */

    printf("ptr : %d\n", sizeof(ptr));
    printf("*ptr: %d\n", sizeof(*ptr));

    return 0;
}

20.4.1.9 キャスト(型変換)

20.4.1.10 ? : : 条件演算子

下のように ?: を用いて, 与えられた条件が成立するとに 使用する値と, 成立しない時に使用する値を 簡潔に表現できる.

(条件式) ? (条件式が真の時の値) : (条件式が偽の時の値)

例えば次のように利用できる. abs_x には x の絶対値が代入 されることになる. x>=0 が真の時 x が 偽の時 -x が 使われることに注意.

#include <stdio.h>

int main(void)
{
    int x, abs_x;

    x = 3;
    abx_x = (x>=0) ? x : -x;
    printf("|x| = %d\n", y);

    return 0;
}

次のようにして, 2つの数の大きい方(max)もしくは小さい方 (min)を得ることもできる.

#include <stdio.h>

int main(void)
{
    int x, y, max, min;

    x = 3;
    y = 7;

    max = (x>=y) ? x : y;
    min = (x<y)  ? x : y;
    printf("max = %d, min = %d\n", max, min);

    return 0;
}

次の例では, 1 から 100 までの数字を 1 行に 10 個ずつ表示する. printf 関数の出力フォーマットの %s の部分には, i%10==0 が成立するとき, すなわち i が 10 で割り切れるとき, "\n" が渡され, そうでない時 " " が渡される.

#include <stdio.h>

int main(void)
{
    int i;

    for(i=1; i<=100; i++)
        printf("%d%s", i, (i%10==0) ? "\n" : " ");

    return 0;
}

20.4.1.11 ,: コンマ演算子

20.4.1.12 []: 添字演算子

[]は配列参照を表す. array[i]*((array)+(i)) に等しい.

20.4.1.13 ()関数呼び出し

20.4.1.14 $->$ . 構造体の参照

->. は構造体もしくは共用体の参照を表す. a->member は (*a).member に等しい.

20.4.2 異なる型の数値の演算や代入

20.4.3 計算の精度


20.5 制御


20.5.1 文とブロック

一文だけの時は { }は無くてもよい. 複数の文を記述したい時にはそ れらの文を { } で括ることで単一の文と同等に扱える. また, ブロッ クの内部で変数の宣言を行なうことができる  [20.3.3]. 「{ }」の後ろに 「;」は必要ない.

例えば,

#include <stdio.h>

int main(void)
{
    int i;

    for(i=0; i<10; i=i+1)
        printf("aaa.\n");

    return 0;
}
for ループの中で複数の文を実行したいときには,
#include <stdio.h>

int main(void)
{
    int i;

    for(i=0; i<10; i=i+1){
        printf("aaa.\n");
        printf("bbb.\n");
        printf("ccc.\n");
    }

    return 0;
}

慣れないうちは 1文だけでも常に { } で括っておいた方が誤りが少な い.

何も実行しないときには, ; だけからなる ``空文'' を用いる. 例え ば,

int main(void)
{
    int i;

    for(i=0; i<10; i++)
        ;
}


20.5.2 制御に使われる ``式''

制御に使われる ``式'' には下に挙げる式の値が使われる. 式の値が 0 の時 ``偽'' (条件が不成立), 0でないとき ``真'' (成立) を意味する.

20.5.3 if

20.5.4 for

無限ループ

for(;;){
    ...
}

20.5.5 while

20.5.6 do-while

20.5.7 switch

case に使えるのは, 文字と... 脱出には, break, return, exit 等が有り得る. 必要なさそうでも安全のため break を付けておく.


20.5.8 break: 終了

break; を用いることにより switch{ } から抜け 出せることは既に述べた. それ以外にも for, while, do-while からも break; により脱出する ことができる. すわなち for, while, do 自体の実行を終了する.

次の例では, a と 一致する配列要素を探す. 前から順に比較し, 最初に見つ かった時点で検索を終了する.

#include <stdio.h>

int main(void)
{
    int i, a;
    int x[10] = {3, 5, 7, 6, 0, 4, 2, 1, 9, 8};

    a = 6;
    for(i=0; i<10; i++){
        if(x[i] == a){
            printf("x[%d] is equal to a.\n", i);
            break;
        }
        printf("x[%d] is not equal to a.\n", i);
    }

    return 0;
}
x[i]==a となる x[i] が見つかったら for ループの { } 内の実行をやめる.


20.5.9 continue: 次のループ

continue; は, for, while, do-while のループ内部を実行している 最中に, そのループを終了する.

次の例では, 配列の中から負でない実数だけを選別して, その和と数を求めて いる.

#include <stdio.h>

int main(void)
{
    double sum, x[4] = {10.0, -2.0, 5.0, 7.0};
    int i, n;

    n = 0;
    sum = 0.0;
    for(i=0; i<4; i++){
        if(x[i] < 0.0)
            continue;
        printf("%d %g\n", i, x[i]);
        sum = sum + x[i];
        n++;
    }

    printf("number = %d, sum = %g\n", n, sum);

    return 0;
}
x[i] < 0.0) の時には, 何もしないで次のループに入っている.


20.5.10 goto: ジャンプ

goto は次のような構文をとり, そのラベルの位置までジャンプします.

goto ラベル;
...
ラベル: 文
ラベルは飛び先に付ける名前で, その後ろに: (コロン)を付けます. ラベル名は変数等と同じように命名できます. ラベルは文につけることになっ ているが, つける文が無いときはlabel: ; のように空文でもいい. ラベルの通用範囲は関数内.

次の例では, 二つの配列 a[5], b[5] の中から重複する要素がないか調べる. 1 つでも一致する要素が見つかった時点で比較を終了する.

#include <stdio.h>

int main(void)
{
    int i, j;
    int a[5] = {8, 4, 7, 1, 9};
    int b[5] = {2, 3, 5, 4, 9};

    for(i=0; i<5; i++){
        for(j=0; j<5; j++){
            if(a[i] == b[j]){
                printf("a[%d] = b[%d] = %d\n", i, j, a[i]);
                goto found;
            }
        }
    }
    found:
        printf("found\n");
    return 0;
}
a[i] == b[j] となったら, found: までジャンプしている. break; は 一番内側のループを終了するだけなので, この場合次の i の値に対するループに入るだけである. このような大域脱出には goto は有用である.

goto は, プログラムの構造を分かりにくくするので, 使わない方がい いことの方が多い.

20.5.11 longjmp

longjump, setjmp.

20.6 関数

20.6.1 アドレスを引数とする関数呼び出し

アドレスを仮引数とする関数呼び出しでは, 呼び出し側の変数の値が変わる. 従って, 変数の遮蔽が行えないことに注意する必要がある.

以下に, アドレスを仮引数とする関数呼び出しの例を示す.

#include <stdio.h>

void func(int *x)
{
	 printf("*x = %d\n", *x); // *x の値.
	 printf(" x = %p\n",  x); // x が指すアドレス
	 *x = 10;
	 printf("*x = %d\n", *x); // *x の値.
}

int main(void)
{
	 int a = 3;

	 printf(" a = %d\n",  a); // a の値
	 printf("&a = %p\n", &a); // a のアドレス
	 func(&a);
	 printf(" a = %d\n",  a);

	 return 0;
}

  1. main関数内で, 通常の変数 int a;を宣言している. この変数の値はaアドレスは&aで得られる. printfの書式には, アドレスを出力するときは %pを用いる.
  2. func(&a);により関数funcを呼び出している. ここで, 変数名aの前に&を付けることにより, 関数funcに変数aのアドレスを渡している.
  3. 関数funcは, 仮引数としてint *xを受け取っている. ここでxは, アドレスを受け取るポインタ変数である. ここでは, main関数中の変数aのアドレスを受け取り, xaのアドレスが代入される.
  4. ポインタ変数xの前に*を付けることで, xが指すアドレスに格納されている値を参照したり, 代入したりすることができる. *xが通常の変数に相当する. ここでは, xは, main関数中のaを指しているので aに代入が起き, main関数中のaの内容が書き変わる.


20.6.2 可変長引数の関数

printf 関数 や scanf 関数には任意の数の引数を渡すことが できる. このような「可変長引数」の関数を作ることもできる.

  1. va_start により 引数へのポインタ argptr を可変引数 の引数のうち最初の引数のアドレスに設定する. そのために, 可変引数の一 つ前の引数のアドレスを参考にする. 引数はメモリー(スタック)上で順番に 並んでいることが保証されているので, このようなことが可能となる.

  2. 関数から戻る前に, va_end により後始末をしておくことが必要 である.

可変引数の一つ前の引数とは, すなわち 「...」 の前の引数のことで, ここ では format のこと. 可変長引数をとる関数では, このような可変でない引数 が必ず 1つ必要になる.

#include <stdio.h>
#include <stdarg.h>

void my_printf(char *format, ...)
{
    va_list argptr;

    va_start(argptr, format);
    vprintf(format, argptr);
    va_end(argptr);
}

int main(void)
{
    double x, y;

    x = 10.0;
    y = 20.0;

    my_printf("(%g, %g)\n", x, y);

    return 0;
}

自分で受け取った引数を処理するには次のようにする. 可変長引数の関数は, 受け取った引数の型や数を与えられていなければならない. 固定の引数, 大域 変数, もしくは暗黙の仮定により, データの型や数が分るようにしておく. printf 関数および scanf 関数では, 固定の第一引数でそれに 続く引数の型及び個数を指定するようになっている. 次の例でも同様に第一引 数で指定している. format に与える文字列に, 可変長の引数の型を順 に文字 d, f, s で表現して渡している.

実際に値を取り出すには, va_arg を用いる. 例えば取り出す値が int 型の時には va_arg(argment_pointer, int) のようにする. この時 ポインター argment_pointer は自動的に次の変数を指すように設定さ れる.

#include <stdio.h>
#include <stdarg.h>

void my_printf(char *format, ...)
{
    va_list argument_pointer;
    char *ptr, *string_value;
    int integer_value;
    double double_value;

    va_start(argument_pointer, format);

    for(ptr=format; *ptr; ptr++){
        switch(*ptr){
        case 'd':
            integer_value = va_arg(argument_pointer, int);
            printf("integer: %d\n", integer_value);
            break;
        case 'f':
            double_value = va_arg(argument_pointer, double);
            printf("dobule: %g\n", double_value);
            break;
        case 's':
            string_value = va_arg(argument_pointer, char *);
            printf("string: ");
            while(*string_value)
                putchar(*string_value++);
            printf("\n");
            break;
        default:
            break;
        }
    }
    va_end(argument_pointer);
}

int main(void)
{
    my_printf("fs", 5.3, "aaa bbb ccc");
    
    return 0;
}

次の例では, 受け取った変数の値の平均値を求める. 変数の数は幾つでも良く, その数は第一の固定の引数で渡す.

#include <stdio.h>
#include <stdarg.h>

double average(int n, ...)
{
    va_list argument_pointer;
    int i;
    double sum = 0.0;
    
    va_start(argument_pointer, n);
    for(i=0; i<n; i++)
        sum += va_arg(argument_pointer, double);
    va_end(argument_pointer);

    return sum/n;
}

int main(void)
{
    double avr;

    avr = average(3, 3.0, 4.0, 5.0);
    printf("average = %g\n", avr);
    
    avr = average(5, 3.0, 4.0, 5.0, 6.0, 7.0);
    printf("average = %g\n", avr);
    
    return 0;
}

20.6.3 再帰呼び出し

関数はその内部で自分自身を呼び出すことができる. これを再帰呼び出し (recursive call)という.

次の例は, $n!$ を計算するプログラムで, fact 関数の中で再帰的に fact 関数を呼び出している. $n$ 回目の呼び出しと $n+1$ 回目の呼 び出しで使用される関数内部で宣言されている変数 ret は, 各呼び出 しごとに別の領域に実体が確保されることが保証されている. これにより, $n$ 回目の呼び出しで変数 ret に代入を行なっても, $m$ 回目の呼び 出しで使用される変数 ret には影響を及ぼすことはない.

#include <stdio.h>

int fact(int n)
{
    int ret;

    if( n == 1)
        ret = 1;
    else
        ret = n * fact(n-1);

    return ret;
}

int main(void)
{
    int a, b;

    a = 4;
    b = fact(a);
    printf("%d ! = %d\n", a, b);

    return 0;
}


20.6.4 関数へのポインター

関数の実体も計算機上のあるアドレスから始まる領域にあるはずである. そ の関数の関数名そのものがそのアドレスを指すポインターとして自動的に用意 されている. 次の例では add$add$ という関数を指 すポインターになっている.

#include <stdio.h>

double add(double a, double b)
{
    return x + 10.0;
}

int main(void)
{
    add(10.0, 5.0);
    return 0;
}
このような関数名からなるポインターは変更してはいけない.

この関数を指すためのポインター変数 fn を用意すると, 次のようにそれが指 す関数を呼び出すことができる.

#include <stdio.h>

double add(double a, double b)
{
    return a + b;
}

double sub(double a, double b)
{
    return a - b;
}

int main(void)
{
    double x;
    double (*fn)(double a, double b);

    fn = add;
    x = (*fn)(10.0, 5.0);
    printf("x = %g\n", x);

    fn = sub;
    x = (*fn)(10.0, 5.0);
    printf("x = %g\n", x);

    return 0;
}
この例のように, 関数の型が一致するよう関数へのポインターを宣言する必要 がある.

更に, 関数名を受け取る関数を作ることができ, 応用の範囲が広がる.

#include <stdio.h>

void calc(double (*fn)(double a, double b), double a, double b)
{
    double x;

    x = (*fn)(a, b);
    printf("x = %g\n", x);
}

double add(double a, double b)
{
    return a + b;
}

double sub(double a, double b)
{
    return a - b;
}

int main(void)
{
    calc(add, 10.0, 5.0);
    calc(sub, 10.0, 5.0);

    return 0;
}

関数へのポインターを用いて関数呼び出しを行なう際には, (*fn)(a, b) のように記述したが, 通常の関数呼び出しと同様に fn(a, b) としても構わない. 前者の方が, ポインターを用いているこ とが見ただけで明らかとなるので いいかも知れない.

20.6.5 static による翻訳単位外からの関数の遮蔽


20.7 プログラムを呼び出した側とのやりとり

プログラムはそれを呼び出したところから 引数を受け取り, 整数の戻り値を 返す. それ以外に「環境変数」という変数に設定された文字列を得ることがで きる.

プログラムの実行が始まると最初に呼び出される関数が main 関数で, これを プログラム内で 1 つ用意する必要がある. $main$ 関数は引数をプログラム を呼び出した所から から受け取り, そこへ戻り値を返す. プログラムを呼び 出すところとは, 端末からの入出力を実際に行なっているシェルであったり, 場合によっては別のプログラムであったりする.


20.7.1 main 関数の引数

#include <stdio.h>

/* argc はコマンドラインの引数の数を表す.
   argv は引数の実体を指す. */
int main(int argc, char *argv[])
{
    int i;

    /* argv[0] には実行されているプログラム名が入っている. 
       それ以降の argv[1], argv[2], ... にコマンドラインからの引数が
       入っている. */
    printf("argc (number of arguments): %d\n", argc);
    printf("argv[0] (argument 0): %s\n", argv[0]);
    printf("argv[1] (argument 1): %s\n", argv[1]);
    printf("argv[2] (argument 2): %s\n", argv[2]);

    return 0;
}
このプログラムをコンパイルした後
$ ./a.out aaa bbb -l ccc -f ddd
のようにオプションとして幾つかの文字列を与えてみると動作を確認すること ができる. この例では, コマンドラインから与える文字列の数が少なすぎると エラーになるので注意が必要である.

$for$ [19.6.2] を知っている人は次の例の方が汎用性が高い.

#include <stdio.h>

/* argc はコマンドラインの引数の数を表す.
   argv は引数の実体を指す. */
int main(int argc, char *argv[])
{
    int i;

    /* argv[0] には実行されているプログラム名が入っている. 
       それ以降の argv[1], argv[2], ... にコマンドラインからの引数が
       入っている. */
    for(i=0; i<argc; i++)
        printf("argument %d: %s\n", i, argv[i]);

    return 0;
}

OS から引数を受け取らないときには main 関数は

int main(void)
{
    ...
と宣言しておく.


20.7.2 main 関数や exit 関数の戻り値

main 関数から戻るということはプログラム終了することを意味する. またプ ログラムの任意の位置で $exit$ 関数によりプログラムを終了することができ る. この時 整数値が return 文や exit() によりプログラムを呼び出 したところに返される. 一般に, 0 は正常終了を意味し, それ以外の場合に は何らかの異常な終了を意味することになっている. この返された値の参照の 仕方は呼び出し方に依存する. 例えば  [23.4.6]参 照のこと.

次のような プログラムを作って実行してみると動作を確かめることができる.

#include <stdio.h>

int main(void)
{
    int ret;

    scanf("%d", &ret);
    printf("ret = %d\n", ret);

    return ret;
}
このプログラムが返した値の参照の仕方については, [23.4.6] 参照のこと.

汎用性を高めるために stdlib.h の中で

#define EXIT_FAILURE    1
#define EXIT_SUCCESS    0
と定義されている. 例えば,
#include <stdlib.h>

int main(void)
{
    /* .... */
    return EXIT_SUCCESS;
}


20.7.3 環境変数

次のプログラムは, 環境変数PATH を取得する.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *path;

    path = getenv("PATH");
    printf("%s\n", path);

    return 0;
}


20.8 ライブラリー関数


20.8.1 printf

#include <stdio.h>

int printf(const char *format, ...);

$printf$ 関数は format で渡される文字列を標準出力に出力する. format の中には %で始まる書式制御文字列を含めることができる. 各書式制御文字列の位置には, 「...」の部分に渡された可変長の引数が, 順 に渡され出力される.

$printf$ 関数の書式制御文字列は一般に

%[フラグ][[0]w][.n][修飾子]型
の形をとる. ここで [] は省略可能であることを意味する.

フラグ
- フィールドの左端に揃えて出力.
+ + もしくは - の符号を付ける.
(スペース) 正の数の場合, 符号の出力位置に空白を出力.
データの前に o の時 0 を, x の時 0x を, X の時 0X を付ける. f,e,E,g,G の時必ず小数点を付ける. g, G の時後尾の0を削除しない.

[0]w
フィールド幅. 0 が付いているときには, 左側を 0 でパディ ングする.

.n
小数点部分の桁数.

修飾子
h d, i, o, u, x, X に対し short int を指定する.
l d, i, o, u, x, X に対し long int を, e, E, f, g, G に対し double を指定する.

d,i int; 符合付き10進数.
o int; 符合無し8進数.
x,X int; 符合無し16進数. (x は abcdef, X は ABCDEF)
u int; 符合無し10進数.
c int; unsigend char に変換されたの値の文字
s char *; 文字列.
f double; [-]$mmm.ddd$ の形の10進数. 小数点以下の桁数は指定しない 場合は6桁.
e,E double; e の時 [-]$m.ddd$e$\pm$$xx$, E の時 [-]$m.ddd$E$\pm$$xx$ の10進数.
g,G double; 指数に応じて %f と %e 又は %E が使い分けられる. g の 時 %e, G の時 %E が使われる. 後ろに続く 0 は表示されない.
p void *; ポインター.
n int *; ここまでで書き出された文字の数が引数に書き込まれる.
% % 文字自身.

例:

printf("%lf\n", x);
printf("%10.5lf", x);

%n の例:

#include <stdio.h>

int main(void)
{
    int n;

    /* n には %n が現れるまでに出力した文字数 即ち 5 が代入される. */
    printf("Hello%n\n", &n);
    printf("n = %d\n", n);
    return 0;
}

sprintf (buf, "%0.*f", spin_button->digits, spin_button->adjustment->value);


20.8.2 fprintf

#include <stdio.h>

int printf(FILE *stream, const char *format, ...);

$fprintf$ 関数は format で渡される文字列を stream で指定される ファイルに出力する. format の中には %で始まる書式制御文字列を 含めることができる. 各書式制御文字列の位置には, 「...」の部分に渡され た可変個の引数が, 順に渡され出力される.

See Also: [20.8.1].


20.8.3 scanf

#include <stdio.h>
int scanf( const char *format, ...);

$scanf$ 関数は、format で指定される書式に従って標準入力から変数への読 み込みを行なう. 書式は 「%」で始まる変換文字列を含めることができ, 各 変換文字列には, 「...」の部分で渡された可変個の引数が, 順に渡される. 引数には, 変数のアドレスを渡す必要があることに注意する必要がある.

$printf$ 関数の書式制御文字列は一般に

%[*][w][修飾子]型
の形をとる. ここで [] は省略可能であることを意味する.

対応する入力フィールドをスキップする.

w
データを幅$w$ で区切って入力を行なう.

修飾子
h d, i, o, u, x に対し short int を指定する.
l d, i, o, u, x に対し long int を指定する. e, f, g に対し double を指定する.
L e,f, g に対し long double を指定する.

d int *; 符合付き10進数.
i int *; 整数. 8進数でも16進数でもよい. (0, 0x, 0X で区別.)
o int *; 8進数.
x int *; 16進数.
u unsigned int *; 符合無し10進数.
c char *; 文字
s char *; 非空白文字からなる文字列.
e,f,g float *; 浮動小数点数. (double * ではないことに注意.)
p void *; ポインター.
n int *; ここまでで読み出された文字の数が引数に書き込まれる.
[...] 括弧内に含まれる文字からなる最大の文字列.
[^...] 括弧内に含まれるない文字からなる最大の文字列.
% % 文字自身.


20.8.3.1 scanf の使い方

double 型の値を読み込む時には %e, %g でななく %lf を使うことに注意.

scanf("%lf", &x);

例えば "1/1/99" のように / で区切られた数値は "%d / %d / %d" により 読み込まれる.

#include <stdio.h>

int main(void)
{
    int a, b, c;

    scanf("%d / %d / %d", &a, &b, &c);
    printf("a=%d, b=%d, c=%d\n", a, b, c);

    return 0;
}

「10.0, 20.0」 のように x 座標 y 座標の組のデータが, コンマで区切ってある ことがよくあるが, このような時には同様に 入力フォーマットとして "%lf, %lf" のように「,」を入れておけばよい.

#include <stdio.h>

int main(void)
{
    double x, y;

    scanf("%lf, %lf", &x, &y);
    printf("x=%g, y=%g\n", x, y);

    return 0;
}

多くのデータを 入力が終了するまで読み込むには, $scanf$ 関数の戻り値を利 用する. 終了時に $scanf$EOF (End Of File) を返す. これは, <stdio.h> の中で定義されている.

#include <stdio.h>

int main(void)
{
    double x;

    /* 戻り値が EOF でない間読み込を繰り返す. */
    while(scanf("%lf", &x) != EOF)
        printf("input = %g\n", x);

    return 0;
}
端末から「入力の終了」をプログラムに入力するためには, 例えばCtrl-D(Ctrlキーを押しながらD)を入力する. 実際には, 入力する仕方 [23.4.2] は環境や設定に依存する.

この例では, エラー処理を行なっていないので %lf で受け取れない数又は文字を入力してしまわないように注意する必要がある. そのような入力を行なってしまうと, それが正常に読み込まれるまで次の入力を読み込むことができない. すなわち入力バッファー上に残ったままになる. 上のプログラムでは, そのような対策を行なっていないので無限ループに陥る.

$scanf$ 関数は正常に読み込んだ入力要素の個数を返すので, これが読み込もうとしている個数と一致するかどうかで正常な読み込みを行なえたかどうかを判定できる. 読み込みに失敗した場合に それを捨ててしまうこともできる.

#include <stdio.h>

int main(void)
{
    int ret;
    double x;

    /* 戻り値が EOF でない間読み込を繰り返す. */
    while( (ret=scanf("%lf", &x)) != EOF){
        if(ret==1){
            /* ここで 入力された値を用いる. */
            printf("input = %g\n", x);
        } else {
            /* scanf で読み込めない値を 読んで捨てる. */
            getchar();
            printf("invalid input.\n");
        }
    }

    return 0;
}


20.8.4 fscanf

See Also: 20.8.3.

データの個数が不定である場合には, ファイルの終りまで読んだがどうかを調 べながら読み込めばいい. データを読んだときには, $fscanf$ 関数は読み込 んだデータの数を返す. また, ファイルの終りに辿り着いてもう読み込めない ときには, EOF という定数を返す. 従って, EOF を返さない間デー タを読み続ければいい. EOFの値は <stdio.h>で定義されている.

#include <stdio.h>

int main(void)
{
    FILE *fp;
    double x;
    int n;

    fp = fopen("data.dat", "r");
    if(fp == NULL)
        exit(0);

    while( fscanf(fp, "%lf", &x) != EOF ){
        printf("%g\n", x);
    }

    fclose(fp);
    return 0;
}

しかし, この例では文字のように実数として読み込めないデータが入っている 場合無限ループに入ってしまう. 読めないデータの読み込みを何度も試みるか らである.

次のように, $fscanf$ 関数で読み込めなかったら, $fgetc$ で1文字ずつ読ん で捨ててしまえばいい.

#include <stdio.h>

int main(void)
{
    FILE *fp;
    double x;
    int n;

    fp = fopen("data.dat", "r");
    if(fp == NULL)
        exit(0);

    while( (n=fscanf(fp, "%lf", &x)) != EOF ){
        if( n == 1 ){
            printf("%g\n", x);
        } else {
            fgetc(fp);
        }
    }

    fclose(fp);
    return 0;
}
ここでは, scanf 関数が返す値をまず n に代入する. その代入式自身はは代 入された値を持っているので, それが EOF と等しいか調べている. 正常に読 み込めたときには, 読み込めたデータの数この場合 「1」 を返す.

次に x座標, y座標の値が 1行に入っている次のようなファイルの中身を読み 込むプログラムを作ってみる.

---data.dat の中身---
12 15
13 30
20 20
----------------------

誤ったデータが入っていない場合には次のようにして読み込める.

#include <stdio.h>

int main(void)
{
    FILE *fp;
    double x, y;
    int n;

    fp = fopen("data.dat", "r");
    if(fp == NULL)
        exit(0);

    while( fscanf(fp, "%lf", &x, &y) != EOF ){
        printf("%g %g\n", x, y);
    }

    fclose(fp);
    return 0;
}

先ほどの延長で次のようなエラー処理があるプログラムが可能かも知れないが, これはうまく動作しない.

#include <stdio.h>

int main(void)
{
    FILE *fp;
    double x, y;
    int n;

    fp = fopen("data.dat", "r");
    if(fp == NULL)
        exit(0);

    while( (n=fscanf(fp, "%lf %lf", &x, &y)) != EOF ){
        if( n == 2 ){
            printf("(%g, %g)\n", x, y);
        } else {
            /* fscanf で読み込めなかったとき. 1文字ずつ読み飛ばす. */
            fgetc(fp);
        }
    }

    fclose(fp);
    return 0;
}

データが次のように誤っている場合を考える.

---data.dat の中身---
12 15
ff 30
20 50
----------------------
``ff'' の所で読み込みに失敗するが, $scanf$ 関数は次の 「30」 と その次の行の「20」を(x, y) の組として読み込んでしまう. $fscanf$関数は はスペース, タブ, 改行を個々のデータの区切りと見做しているので, 「ff」 を飛ばした後, 「30」と次の行の「50」を x,y の組として読み込んでしまう からである.

行を行として読み込むことが重要であるので, 一旦1行文を行として読み込んで, そこから値を得るのがいい.

#include <stdio.h>

#define FSCANF_BUFFER_LEN 128

int main(void)
{
    FILE *fp;
    char str[FSCANF_BUFFER_LEN];
    double x, y;


    fp = fopen("data.dat", "r");
    if(fp == NULL)
        exit(0);

    while( fgets(str, sizeof(str), fp) != NULL ){
        if( strlen(str) == (sizeof(str)-1) ){
            fprintf(stderr, "string buffer too small\n");
            continue;
        }

        if( sscanf(str, "%lf %lf", &x, &y) == 2 ){
            printf("(%g  %g)\n", x, y);
        } else if( sscanf(str, "%lf, %lf", &x, &y) == 2 ){
            printf("(%g, %g)\n", x, y);
        } else {
            fprintf(stderr, "Invalid:%s", str);
        }
    }

    /* fgets は EOF の時以外にエラーの時も NULL を返す.*/
    if(ferror(fp))
        perror("fscanf");

    fclose(fp);
    return 0;
}

ここでは, 一時的に読み込むため領域 str の大きさ FSCANF_BUFFER_LEN がプログラムを作成した時点で固定されてしまう. 十分に大きくしておけばいいかも知れないが, プログラム実行時に必要に応じ て大きさを変更していく方がいいかも知れない. その例を [21.14] に示す. 柔軟に対応できるようにした結果プログラム は長くなり, 保守性が悪くなったり, 誤りを含む可能性が高くなったりする. また, 作成自身にも時間がかかる. このように必ずしも多機能である方が優れ ているというわけではない. 用途, ハードウェア, 汎用性, 移植性, 速度等を 考慮するバランス感覚が重要である.


20.8.5 fgets

buffer の大きさとしては BUFFSIZ が自然.

#include <stdio.h>

void load_file(char *filename)
{
    FILE *fp;
    char buf[BUFSIZ];

    if((fp=fopen(ewin->filename, "r")) == NULL){
        fprintf(stderr, "cannot open file %s", filename);
        exit(1);
    }

    while(fgets(buf, BUFSIZ, fp) != NULL){
        puts(buf);
    }
}

20.8.6 sscanf


20.8.7 入出力関数のまとめ

  標準入力 (stdin) ファイル 文字列
char getchar fgetc  
char (macro)   getc  
stirng gets fgets  
format scanf fscanf sscanf
var-format vscanf vfscanf vsscanf

  標準出力 (stdout) ファイル 文字列
char putchar putc  
char (macro)   fputc  
stirng puts fputs  
format printf fprintf sprintf
n-format     snprintf
var-format vprintf vfprintf vsprintf
var-n-format     vnsprintf

20.8.8 malloc: メモリーを確保する

配列の大きさの決定の方法は次の3つがある.

次に, mallocにより領域を確保した配列の使用例を示す.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *ptr;
    int i, n;

    /* n はプログラム中で自由に変更できる変数であることに注意. */
    n = 10;

    /* ここでは, int 型の配列として用いる.
       malloc には必要な領域のバイト数を渡す.
       これは, sizeof(int) で得られる int 型の変数の大きさと,
       要素数 (ここでは n) の積で得られる.
       malloc 関数は大きさとして size_t 型を用いるので
       n は, int 型でなく, size_t 型の方がいいかも知れない.
       malloc は確保した領域の先頭のアドレスを返す. int 型で使うには,
       (int *) により, int 型変数へのポインターに キャストして
       ポインター (ここでは ptr) で受け取る. */

    ptr = (int *)malloc(n * sizeof(int));

    /* 領域の確保に失敗したときには malloc は NULL を返す.*/
    if(ptr == NULL){
        perror("ptr");
        exit(1);
    }

    /* 以降は 配列と同様に利用できる. */
    for(i=0; i<10; i++)
            ptr[i] = i * 100;

    for(i=0; i<10; i++)
        printf("ptr[%d] = %d\n", i, ptr[i]);

    /* 使用を終えたら領域の解放を行なう */
    free(ptr);

    return 0;
}


20.8.9 数学関数

数学関数とそれに関連したマクロは <math.h>で宣言されている.

関数 説明
double sin(double x);  
double cos(double x);  
double tan(double x);  
double asin(double x); $[-\pi/2, \pi/2]$, $x \in [-1, 1]$ の sin の逆関数
double acos(double x); $[0,\pi]$, $x \in [-1, 1]$ の cos の逆関数.
double atan(double x); $[\pi/2, \pi/2]$ の tan の逆関数.
double atan2(double y, double x); 範囲 $[-\pi, \pi]$ $\tan^{-1}(y/x)$
double sinh(double x);  
double cosh(double x);  
double tanh(double x);  
double exp(double x);  
double log(double x); $\log_e x$
double log10(double x); $\log_{10} x$
double pow(double x, double y); $x^y$ (C言語では, x**yx^yの表記は無い.)
double sqrt(double x); $\sqrt{x}$
double ceil(double x); $x$ より小さくない最小の整数.
double floor(double x); $x$ より大きくない最大の整数.
double fabs(x); $\vert x\vert$
double ldexp(double x, int n); $x \cdot 2^n $
double frexp(x, int *n); $x = y \times 2^n$ となり かつ x の仮数部が [1/2, 1) の範囲の浮動小数点数となるように規格化する. その時 の y の値を関数値として返し, n の値をセットする.
double modf(double x, double *ip); $x$ の小数部分を関数値として返し, 整数部分を p に返す.
double fmod(double x, double y); x を y で割った余りを返す.

エラーの種類 errno の値
領域エラー(引数が範囲外) EDOM
範囲エラー(返り値が範囲外) ERANGE


20.8.10 文字列関数

is_alpha, ...

char *strcpy(char *dest, const char *src);  
char *strncpy(char *dest, const char *src, size_t n);  
char *strcat(char *dest, const char *src);  
char *strncat(char *dest, const char *src, size_t n);  
int strcmp(const char *s1, const char *s2);  
int strncmp(const char *s1, const char *s2, size_t n);  
char *strchr(const char *s, int c);  
char *strrchr(const char *s, int c);  
size_t strspn(const char *s, const char *accept);  
size_t strcspn(const char *s, const char *reject);  
char *strpbrk(const char *s, const char *accept);  
char *strstr(const char *haystack, const char *needle);  
size_t strlen(const char *s);  
char *strtok(char *s, const char *delim);  
void *memcpy(void *dest, const void *src, size_t n);  
void *memmove(void *dest, const void *src, size_t n);  
int memcmp(const void *s1, const void *s2, size_t n);  
void *memchr(const void *s, int c, size_t n);  
void *memset(void *s, int c, size_t n);  
char *strdup(const char *s);  
int strcasecmp(const char *s1, const char *s2);  
int strncasecmp(const char *s1, const char *s2, size_t n);  
int strcoll(const char *s1, const char *s2);  
char *strfry(char *string);  
char *strsep(char **stringp, const char *delim);  
size_t strxfrm(char *dest, const char *src, size_t n);  
char *index(constchar*"s, int c);  
char *rindex(const char *s, int c);  

20.8.11 perror

errno, perror.


20.9 複素数

C99では,複素数を扱うことができる.

20.9.1 複素数の使い方

次の例では, 複素数a$3+4i$を代入し, その実部,および虚部を出力している.
// 複素数は C99 で使用できるようになった.
#include <stdio.h>
#include <complex.h>

int main(void)
{
	 double complex a;
	 a = 3.0 + 4.0 * I;                        // I は虚数単位.
	 printf("Real part: %g\n", creal(a));      // creal は実部を求める関数.
	 printf("Imaginary part: %g\n", cimag(a)); // cimag は虚部を求める関数.
	 printf("a = %g + %g i\n", creal(a), cimag(a));
	 
	 return 0;
}

20.9.2 実数と同じ部分

20.9.2.1 複素数同士の演算

実数の四則演算+, -, *, /と同様に演算を行うことができる.

// 複素数は C99 で使用できるようになった.
#include <stdio.h>
#include <complex.h>

int main(void)
{
	 double complex a =  3.0 + 4.0 * I;
	 double complex b = -1.0 + 8.0 * I;
	 double complex c;

	 c = a + b;
	 printf("c = %g + %g i\n", creal(c), cimag(c));

	 c = a - b;
	 printf("c = %g + %g i\n", creal(c), cimag(c));

	 c = a * b;
	 printf("c = %g + %g i\n", creal(c), cimag(c));

	 c = a / b;
	 printf("c = %g + %g i\n", creal(c), cimag(c));
	 
	 return 0;
}

20.9.2.2 複素数の配列

// C99

#include <stdio.h>
#include <complex.h>

int main(void)
{
	 double complex a[10];
	 int i;

	 for(i=0; i<10; i++){
		  a[i] = (double) i + (double) i * 2.0 * I;
	 }

	 for(i=0; i<10; i++)
		  printf("a[%d] = %g + %g i\n", i, creal(a[i]), cimag(a[i]));
	 
	 return 0;
}

20.9.2.3 複素数を引数,戻り値とする関数

// C99

#include <stdio.h>
#include <complex.h>

double complex add(double complex a, double complex b)
{
	 double complex z;

	 z = a + b;
	 return z;
}

int main(void)
{
	 double complex a = 3.0 + 4.0 * I;
	 double complex b = 8.0 + 9.0 * I;
	 double complex c;

	 c = add(a, b);
	 
	 printf("c = %g + %g i\n", creal(c), cimag(c));

	 return 0;
}

20.9.2.4 複素数変数へのポインター

// C99

#include <stdio.h>
#include <complex.h>

double complex *func(double complex *z)
{
	 int i;

	 for(i=0; i<10; i++)
		  z[i] += 5.0 * I;

	 return z;
}

int main(void)
{
	 double complex a[10];
	 int i;

	 for(i=0; i<10; i++)
		  a[i] = (double)i + (double)i * I;

	 for(i=0; i<10; i++)
		  printf("a[%d] = %g + %g i\n", i, creal(a[i]), cimag(a[i]));

	 func(a);
	 
	 for(i=0; i<10; i++)
		  printf("a[%d] = %g + %g i\n", i, creal(a[i]), cimag(a[i]));

	 return 0;
}

20.9.3 複素数を扱うための型

複素数の変数は, キーワードcomplexを用いて宣言する. 実部, 虚部の精度がともに倍精度実数doubleと同じである複素数の変数は, double complex により宣言する.

同様にして, 実部,虚部が float型, double型, long double型である複素数を次のように宣言できる.

20.9.4 複素数を扱うための基本的な関数

// C99

#include <stdio.h>
#include <complex.h>

int main(void)
{
	 double complex z = 3.0 + 4.0 * I;
	 printf("real part: %g\n", creal(z));
	 printf("imaginary part: %g\n", cimag(z));
	 printf("complex absolute value: %g\n", cabs(z));
	 printf("argument (phase angle): %g\n", carg(z));
	 printf("complex comjugate: %g + %g i\n",
			creal(conj(z)), cimag(conj(z)));
	 return 0;
}

20.9.5 複素数を扱うための数学関数

実数を引数とし, 実数を返す数学関数に対応して, 複素数を引数とし, 複素数を返す関数が用意されている.

次の例は, 複素数$a$に対して $e^a$を求め$b$に代入するプログラムである.

// C99

#include <stdio.h>
#include <complex.h>

int main(void)
{
	 double complex a = 0.3 + 0.4 * I;
	 double complex b;
	 
	 b = cexp(a);
	 
	 printf("c = %g + %g i\n", creal(b), cimag(b));

	 return 0;
}

gcc でコンパイルする場合は次のように -l オプションが必要であることに注意.

$ gcc standard_complex_function.c -lm

これらは, 関数名の前にcが付いている. 実数には,doubleの他に float, long double がある. また, 複素数には, double complexの他に float complex, long double complex がある. これらは, 関数名の最後にf, lが付く関数が用意されている.

もう一度整理してみると, 次のようになる.

数学関数は, 引数の型として float, double, long double, float complex, double complex, long double complex があり, 区別して使用するのは煩雑である. そこで, sinf, sin, sinl, csinf, csin, csinlを全て sinと表記するだけでよい仕組みが用意されている. tgmath.hをインクルードしておくと, 引数の型に応じて適切な関数が選択されるようになっている.

しかし, 実数しか引数に取らないfmaxdouble complex型の引数を渡すなど した場合には, 動作が保証されないので十分に注意する必要がある.


20.10 ワイド文字とマルチバイト文字

20.10.1 まとめ

  ワイド文字 マルチバイト文字
定義 1文字あたりのサイズが一定の文字. 1文字あたりサイズが一定でない文字.
用途 サイズが固定されているため処理が容易. 内部での処理に用いる. 入出力用
wchar_t char
文字 wchar_t wc='A'; char mc='A';
  wchar_t wc=L'漢';  
文字列 wchar_t *wstr = L"ABCDEFG"; char *mstr = "ABCDEFG";
  wchar_t *wstr = L"漢字"; char *mstr = "漢字";
printf wprintf(L"wc=%lc\n", L'A'); printf("mc=%c\n", 'A');
  wprintf(L"wc=%lc\n", L'漢');  
  wprintf(L"wstr=%ls\n", L"ABC"); printf("mstr=%s\n", "ABC");
  wprintf(L"wstr=%ls\n", L"漢字"); printf("mstr=%s\n", "漢字");
scanf wscanf(L"%lc", &wc); scanf("%c", &mc);
  wscanf(L"%ls", wstr); scanf("%s", mstr);
strlen wcslen(L"ABC"); strlen("ABC");
  wcslen(L"漢字");  


1文字を1バイトで表すシングルバイト文字も, マルチバイト文字に含まれる.


相互に変換する関数

ワイド文字 --[int wctomb(char *s, wchar_t wc);]$\rightarrow$ マルチバイト文字
  $\leftarrow$[int mbtowc(wchar_t *pwc, const char *s, size_t n);]--  
ワイド文字列 -[size_t wcstombs(char *dest, const wchar_t *src, size_t n);]$\rightarrow$ マルチバイト文字列
  $\leftarrow$[size_t mbstowcs(wchar_t *dest, const char *src, size_t n);]--  

20.10.2 具体例

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 char *str = "漢字の書き方";
	 printf("漢字\n");
	 printf("%s\n", str);
	 return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 char str[1024];

	 scanf("%s", str);
	 puts(str);
	 return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>


//	 setlocale(LC_ALL,"");

// Linux ...
//	 setlocale(LC_ALL,"ja_JP.UTF-8");

// Windows ...
//	 setlocale( LC_ALL, "Japanese_Japan.932" );
//	 setlocale( LC_ALL, "jpn" );


int main(void)
{
	 setlocale( LC_ALL, "Japanese_Japan.932" );
	 return EXIT_SUCCESS;
}

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
// gcc でコンパイルする場合, -std=c99 オプションを指定する.
// $ cc -std=c99 -Wall wprintf.c


//	 setlocale(LC_ALL,"ja_JP.UTF-8");

// wprintf で wide character の文字列を出力する場合,
// format 中で  l modifier を指定する.
// すなわち %sでなく %ls とする.

int main(void)
{
	 setlocale(LC_ALL,"");
	 wprintf(L"%ls\n", L"漢字");
	 return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
// gcc でコンパイルする場合, -std=c99 オプションを指定する.
// $ cc -std=c99 -Wall wprintf.c

// wscan で wide character の文字列を入力する場合,
// format 中で  l modifier を指定する.
// すなわち %sでなく %ls とする.

int main(void)
{
	 wchar_t str[1024];
	 setlocale(LC_ALL,"");
	 wscanf(L"%ls", str);
	 wprintf(L"%ls\n", str);
	 return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
/*
  プログラムが EUC-JPで書かれている場合 --input-charset=EUC-JP
   $ gcc -Wall  --input-charset=EUC-JP  -std=c99 wchar.c
*/

int main(void)
{
	 wchar_t *str = L"漢字";
	 setlocale(LC_ALL, "");

	 wprintf(L"%ls\n", str);
	 wprintf(L"%ls\n", L"ひらがな");

	 return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>

/*
wcslen - determine the length of a wide-character string
#include <wchar.h>
size_t wcslen(const wchar_t *s);
Conforming to C99.
*/

// $ cc -std=c99 -Wall wprintf.c
//	 setlocale(LC_ALL,"ja_JP.UTF-8");

// wprintf と printf の混在はできないようだ.

int main(void)
{
	 size_t len;
	 wchar_t *str = L"漢字ABCDEFG";

	 setlocale(LC_ALL,"");
	 wprintf(L"%ls\n", str);
	 len = wcslen(str);
	 wprintf(L"wcslen(str)=%d\n", len);

	 return EXIT_SUCCESS;
}
// $ cc --input-charset=UTF-8 --exec-charset=UTF-8 --std=c99 -Wall wctomb_EUC.c 

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <locale.h>

int main(void)
{
    wchar_t *wcs = L"漢字を含む文字列 ABC abc";
	size_t msb_len =  1024;
    char mbs[msb_len];
    int result;

    setlocale(LC_CTYPE, "ja_JP.UTF-8");

    result = wcstombs(mbs, wcs, msb_len);
    if ( result >= 0 ) {
        printf("wcs は正しい文字列です.\n");
        printf("s: %s\n", mbs);
    } else {
        printf("wcs は誤った文字列です.\n");
    }
	
    return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <locale.h>
/*
$ cc --input-charset=EUC-JP --exec-charset=EUC-JP --std=c99 -Wall wctomb_EUC.c 
  ワイド文字をマルチバイト文字に変換し結果を表示する
 */


int main(void)
{
    wchar_t wc = L'漢';
    int result;
    char s[MB_LEN_MAX] = {'\0'};

//    setlocale(LC_CTYPE, "ja_JP.eucJP");
    setlocale(LC_CTYPE, "ja_JP.UTF-8");

    result = wctomb(s, wc);
    if ( result >= 0 ) {
        printf("wc は正しいワイド文字です.\n");
        printf("s: %s\n", s);
    } else {
        printf("wc の値が正しい多バイト文字に対応していません.\n");
    }
	
    return EXIT_SUCCESS;
}

20.11 プリプロセッサ

// if節
#if 定数整数式
#elif
#else
#endif

//ifグループ
#if
#ifdef
#ifndef

// 制御行
#include
#define
#undef
#line
#error
#pragma

defined
 #
 ##


20.11.1 標準定義マクロ

標準で定義されているマクロ. C99, 複素数, 浮動小数点数, UNICODE の対応状況などがセットされている.
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	 /* これ以降 C90 9899:1990 で定義されている. */
	 printf("__LINE__ = %d\n", __LINE__);
	 printf("__FILE__ = %s\n", __FILE__);
	 printf("__DATE__ = %s\n", __DATE__);
	 printf("__TIME__ = %s\n", __TIME__);
	 printf("__STDC__ = %d\n", __STDC__);
	 

#ifdef __STDC_VERSION__
	 /* これ以降 9899/AMD1:1995 で定義されている. */
	 printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);
#endif
	 
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
	 // これ以降 C99 9899:1999 で定義されている.
	 // gcc では -std=c99 オプションが必要.

	 // Cのバージョン
	 printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);

	 // ISO/IEC 10646 (UNICODE)
	 printf("__STDC_ISO_10646__ = %ld\n", __STDC_ISO_10646__);

	 // Annex F (IEC 60559浮動小数点演算)
	 printf("__STDC_IEC_559__ = %d\n",  __STDC_IEC_559__);
	 
	 // Annex G (IEC 60559 互換複素数演算)
	 printf("__STDC_IEC_559_COMPLEX__ = %d\n", __STDC_IEC_559_COMPLEX__);
#endif
	 return EXIT_SUCCESS;
}

20.12 トピックス

ここでは, もう一度様々な視点から C 言語をまとめ直してみる.


20.12.1 標準入出力

プログラムの実行開始時に自動的に 標準入力, 標準出力, 標準エラー出力に対応するファイルがオープンされる. これらは FILE *stdin, FILE *stdout, FILE *stderr を通じて読み書きできる.

printf(format, ...)fprintf(stdout, format, ...) と等 価.

標準エラー出力への出力は次のようにできる. fopenfclose を呼び出す必要はない.

fprintf(stderr, "invalid value: %g\n", x);
作成中.

プログラムの起動時の引数としてファイル名が与えられていたらそのファイル からデータを読み込み, 与えられていなかったら標準入力から読み込むという ときに次のように利用できる.

作成中.

20.12.2 メモリー領域


20.12.2.1 スタック

一時的に変数の内容を保持するために, 「スタック」(stack)と呼ばれるメモ リー上の領域が用いられる. 「auto 変数」や「引数として関数に渡される変 数の値」はスタックに積まれる. これに対し, static 変数や大域変数はスタッ クに積まれない.

(以下 ドラフト) スタック とは push して pop するもの.

C におけるスタックの使い方. auto 変数のための領域. 関数への引数.

20.12.3 プログラムの分割

プログラムを複数のファイル(翻訳単位)に分割すると, 次のような利点は生ま れる.

また, 次の概念が加わる.

static 関数
翻訳単位外からの関数の遮蔽.
ヘッダーファイル
翻訳単位外の関数及び変数の型宣言.
static 大域変数
翻訳単位外からの大域変数の遮蔽.
extern 大域変数
翻訳単位外の変数の使用.
以降では, これらにつき具体的な例を挙げながら説明する. コンパイルの仕方については, [23.6.9] を参照のこと.

20.12.3.1 static 関数

次のような, ファイルからデータを読み込んで, 標準出力 (指定しない場合は 画面) に出力するプログラムを作ったとする. この中では, 標準の fopen 関数と fclose 関数にそれぞれエラー処理を加えた my_fopen 関数と my_fclose 関数を作成した. エラー時の処理 は共通の exit_on_error 関数に行なわせている.

/* ------------------------------- */

#include <stdio.h>
#include <stdlib.h>

void exit_on_error(const char *msg)
{
    perror(msg);
    exit(1);
}

FILE *my_fopen(const char *filename, const char *mode)
{
    FILE *ptr = fopen(filename, mode);
    if(ptr == NULL)
        exit_on_error(filename);
    return(ptr);
}

int my_fclose(FILE *fp)
{
    int ret = fclose(fp);
    if(ret == EOF)
        exit_on_error("my_fclose");
    return ret;
}

#define N 10
int main(void)
{
    double x[N], y[N];
    int i;
    FILE *fp = my_fopen("data.dat", "r");

    for(i=0; i<N; i++)
        fscanf(fp, "%lf%lf", &x[i], &y[i]);

    my_fclose(fp);

    for(i=0; i<N; i++)
        printf("%g, %g\n", x[i], y[i]);

    return 0;
}

/* ------------------------------- */

ここで main 関数とそれ以外の部分の2つに分割し, 別々のファイルに することにする. 分割した後に次のようなことを行なっている.

/* --- utility1.c ----------------------- */

#include <stdio.h>
#include <stdlib.h>

static void exit_on_error(const char *msg)
{
    perror(msg);
    exit(1);
}

FILE *my_fopen(const char *filename, const char *mode)
{
    FILE *ptr = fopen(filename, mode);
    if(ptr == NULL)
        exit_on_error(filename);
    return(ptr);
}

int my_fclose(FILE *fp)
{
    int ret = fclose(fp);
    if(ret == EOF)
        exit_on_error("my_fclose");
    return ret;
}

/* ---main1.c -------------------------- */

#include <stdio.h>

FILE *my_fopen(const char *filename, const char *mode);
int my_fclose(FILE *fp);

#define N 10
int main(void)
{
    double x[N], y[N];
    int i;
    FILE *fp = my_fopen("data.dat", "r");

    for(i=0; i<N; i++)
        fscanf(fp, "%lf%lf", &x[i], &y[i]);

    my_fclose(fp);

    for(i=0; i<N; i++)
        printf("%g, %g\n", x[i], y[i]);

    return 0;
}

20.12.3.2 ヘッダーファイル

先の例では, main1.c の中で my_fopen 関数, my_fclose 関数の型宣言を直接書いた. 複数のプログラムで utility1.c を利用することを考えると, 次のようにする方が効率がい い.

/* --- utility2.h ----------------------- */

#ifndef UTILITY_H
#define UTILITY_H

FILE *my_fopen(const char *filename, const char *mode);
int my_fclose(FILE *fp);

#endif

/* --- utility2.c ----------------------- */

#include <stdio.h>
#include <stdlib.h>
#include "utility2.h"

static void exit_on_error(const char *msg)
{
    perror(msg);
    exit(1);
}

FILE *my_fopen(const char *filename, const char *mode)
{
    FILE *ptr = fopen(filename, mode);
    if(ptr == NULL)
        exit_on_error(filename);
    return(ptr);
}

int my_fclose(FILE *fp)
{
    int ret = fclose(fp);
    if(ret == EOF)
        exit_on_error("my_fclose");
    return ret;
}

/* ---main2.c -------------------------- */

#include <stdio.h>
#include "utility2.h"

#define N 10
int main(void)
{
    double x[N], y[N];
    int i;
    FILE *fp = my_fopen("data.dat", "r");

    for(i=0; i<N; i++)
        fscanf(fp, "%lf%lf", &x[i], &y[i]);

    my_fclose(fp);

    for(i=0; i<N; i++)
        printf("%g, %g\n", x[i], y[i]);

    return 0;
}

/* ------------------------------- */

20.12.3.3 大域変数

もう一つ例を挙げておく. 次のプログラムは実行時に指定される大きさの配列 を確保し, そこに数を代入し表示する. エラー処理を自動的にやってくれる my_malloc 関数と my_free 関数を作成した. 配列の領域を確 保する場合, malloc 関数を用いる手法は, 実行時に大きさを指定でき る点で優れている. 一方で領域の解放が自動的には行なわれないので, メモリー を使用したままの状態にしてしまう.

先程の例と比較し, my_malloc1.c の中で大域変数 int n_alloc; が使用されている点が異なる.

/* ---- my_malloc1.h----------------------- */

#ifndef MY_MALLOC_H
#define MY_MALLOC_H

void *my_malloc(size_t size);
void my_free(void *ptr);

extern int n_alloc;
#endif

/* ---- my_malloc1.c ----------------------- */

#include <stdio.h>
#include <stdlib.h>
#include "my_malloc1.h"

/* 現在確保されているメモリの組の数を保持する. */
int n_alloc = 0;

static void exit_on_error(const char *msg)
{
    perror(msg);
    exit(1);
}

void *my_malloc(size_t size)
{
    void *ptr = (void *)malloc(size);
    if(ptr == NULL)
        exit_on_error("my_malloc");
    n_alloc++;
    return ptr;
}

void my_free(void *ptr)
{
    free(ptr);
    n_alloc--;
}

/* --- main3.c ------------------------- */

#include <stdio.h>
#include "my_malloc1.h"

int main(void)
{
    int n = 10;
    double *x = (double *)my_malloc( n * sizeof(double) );
    double *y = (double *)my_malloc( n * sizeof(double) );
    int i;

    for(i=0; i<n; i++){
        x[] = i;
        y[] = i * 10.0;
    }
    for(i=0; i<n; i++)
        printf("%g, %g\n", x[i], y[i]);

    my_free(x);
    my_free(y);

    /* my_malloc を呼び出した回数と my_free を呼び出した回数は一致する
    はずである. これにより, メモリの解放が全て完了しているか確認できる. */
    printf("n_alloc = %d\n", n_alloc);

    return 0;
}

/* ------------------------------- */

上記の大域変数を用いる方法では, 不用意に n_allocmain3.c 中で書き換えてしまう危険性がある. 大域変数は, 外部から は変更できない方がいい場合が多い. このようなことから, 大域変数は static で修飾して直接参照できなくしておいて, 操作するためのイン ターフェースの関数を用意するのがいい. 下のようにしておけば, 誤って変更 してしまうことはなくなる.

/* ---- my_malloc2.h----------------------- */

#ifndef MY_MALLOC_H
#define MY_MALLOC_H

int get_n_alloc(void);
void *my_malloc(size_t size);
void my_free(void *ptr);

#endif

/* ---- my_malloc2.c ----------------------- */

#include <stdio.h>
#include <stdlib.h>
#include "my_malloc1.h"

/* --- private functions and private variables --- */

/* 現在確保されているメモリの組の数を保持する. */
static int n_alloc = 0;

static void exit_on_error(const char *msg)
{
    perror(msg);
    exit(1);
}

/* --- public functions and public variables --- */

int get_n_alloc(void)
{
    return n_alloc;
}

void *my_malloc(size_t size)
{
    void *ptr = (void *)malloc(size);
    if(ptr == NULL)
        exit_on_error("my_malloc");
    n_alloc++;
    return ptr;
}

void my_free(void *ptr)
{
    free(ptr);
    n_alloc--;
}

/* --- main4.c ------------------------- */

#include <stdio.h>
#include "my_malloc1.h"

int main(void)
{
    int n = 10;
    double *x = (double *)my_malloc( n * sizeof(double) );
    double *y = (double *)my_malloc( n * sizeof(double) );
    int i;

    for(i=0; i<n; i++){
        x[i] = i;
        y[i] = i * 10.0;
    }
    for(i=0; i<n; i++)
        printf("%g, %g\n", x[i], y[i]);

    my_free(x);
    my_free(y);

    /* my_malloc を呼び出した回数と my_free を呼び出した回数は一致する
    はずである. これにより, メモリの解放が全て完了しているか確認できる. */
    printf("n_alloc = %d\n", get_n_alloc());

    return 0;
}

このように, 大域変数は翻訳単位内でのみ直接読み書きできるように static で修飾して外部からは遮蔽するのが望ましい. 翻訳単位外から 値を得ればいいだけであれば, ここで示したように値を得るための関数を用意 すればいい. これで誤って書き換えてしまう危険性が無くなる. 値を書き換え たいときも, 次のような書き換えるための関数を用意しておく方が誤りが少な い.

void set_n_alloc(int n)
{
    n_alloc = n;
}
この関数を用意することで, 対象となる変数が翻訳単位外から書き換えること ができることが明らかとなるからである. (ただしここでは n_alloc を書き換えることは意味をなさない.)

(c)1999-2013 Tetsuya Makimura