関数とマクロ

プログラム開発において,ある処理のまとまりが複数回必要になることがある.C言語では,このような処理のまとまりを関数として記述することができる.処理を関数にすることによって,同じようなコードをいくつも記述する必要がなくなるため開発の効率が向上する.また,同じ処理はひとつの関数に集約されるため,デバッグ作業の効率も向上する.また,比較的小規模な処理は,マクロとして記述することも可能である. ここではC言語における関数とマクロの基本について学習する.

標準ライブラリ関数

これまでに使用してきたprintfなどの関数は,標準ライブラリ関数と呼ばれる.C言語では,main関数を除く全ての関数は,その呼び出しよりも前に定義されるか,プロトタイプ宣言がなされる必要がある.しかし,例えばprintfを定義したり,そのプロトタイプ宣言を明示的に書いた記憶はおそらくないはずである.標準ライブラリ関数を使用するためには,main関数よりも前に,#includeを用いて,関数の定義が書かれたヘッダーファイルを読み込む必要がある.これまでに特に説明もなくプログラムの先頭に#include <stdio.h>と書いていたのはこのためである.以下に主な標準ライブラリ関数のためのヘッダーファイルを示す.

stdio.h

標準入出力.printfscanf,ファイル入出力関数など

stdlib.h

ユーティリティ.exit関数やEXIT_SUCCESSなどのマクロ定義も含まれる.

math.h

数学.三角関数や絶対値,指数関数や対数関数など.

ctype.h

文字列検査関数.isalphaなど.

string.h

文字列操作関数.文字列の連結やコピー,ソートのための大小比較など.

limits.h

整数型に関する最大値や最小値などのマクロ定義.

float.h

浮動小数点型に関する最大値や最小値などのマクロ定義.

time.h

日付および時間.現在時刻の取得や,プログラムの実行時間計測のための関数.

#include <*.h>と書くことを,そのヘッダーファイルをインクルードするという.例えば数学関数を使用するにはmath.hをインクルードする必要がある.< >は,あらかじめシステムに定義されたパスに目的のヘッダーファイルが存在することを意味する.

自前のヘッダーファイルをインクルードするときなど,任意のパスに置かれたヘッダーファイルをインクルードするときには" "を用いる.

ユーザ関数

定義および使用方法

自分で関数を定義するには,

  • 名前

  • 戻り値とそのデータ型

  • 引数の個数と,それぞれのデータ型

を決定する必要がある.C言語では,戻り値は最大で1つまでという制約がある.戻り値なし(すなわち0個)の場合には,その型はvoid型とする.引数の個数には制約は存在しない.

極端に多い引数は,エラーの原因となることがある

関数宣言のフォーマットを図図 1に示す.この図において,引数のリストが,単に「引数」ではなく,なぜ「仮引数」であるのかについては後述する.

{half-width}
図 1. 2関数宣言のフォーマット

例として,2つの整数値を引数として受け取り,その合計を戻り値として返す関数addを考えよう.関数の名前はadd,戻り値の型はint,引数の個数はint型2つである.リストリスト 1に,この関数の定義,および使用例を示す.

リスト 1. 関数の定義例
#include <stdio.h>

int add(int, int);  // 関数のプロトタイプ宣言

int main() {
  int a = 5;
  int b = 10;
  int c = add(a, b);  // 関数addの呼び出し
  printf("%dと%dの和は%dです\n", a, b, c);
  printf("2と3の和は%dです\n", add(2, 3));  // 関数addの呼び出し
}

// 関数の定義(実体)
int add(int x, int y) {
  int z;
  z = x + y;
  return z;  // zの値を戻り値として返す
}

3行目では,関数のプロトタイプ宣言を行っている.プロトタイプ宣言は,関数の定義(実体ともいう)が,main関数よりも後に来る場合には必ず必要になる.

定義された関数を実行することを関数呼び出しという.main関数内において,add関数は,8行目と10行目の2回,呼び出されている.8行目では,add関数によってabの値が加算された結果が17行目のreturn文によって戻り値として返され,cに代入されている.10行目では,2+3の結果,つまり5が戻り値として返ってきている.つまり,add関数を呼び出す側からみると,add()は,その関数の戻り値とみなすことができる.

仮引数による値渡し

先に述べたように,リストリスト 1内のadd関数の定義におけるxyは,仮引数と呼ばれる.これに対して,ab,2や3は,add関数を呼び出す際に与える引数であり,実引数と呼ばれる.C言語では,関数呼び出し時に,実引数のコピーが自動的に作られ,そのコピーに名前を付けたものが仮引数となる.これを値渡しによる関数呼び出しという.実引数には,変数以外にも,式や定数が使えるが,仮引数はその性質上,変数名のみが許されている.

値渡しではない呼び出し方法として参照渡しがあるが,これについてはポインタの回で説明する.

関数の定義内で参照できる仮引数は,あくまで実引数のコピーであるため,仮引数の値を関数内で変更したとしても,呼び出し側の実引数の値は変わらない.以下のコードを用いてこの関係を確認できる.

リスト 2. 実引数と仮引数の関係
#include <stdio.h>

int add2(int, int);  // 関数のプロトタイプ宣言

int main() {
  int a = 5;
  int b = 10;
  int c = add2(a, b);  // 関数addの呼び出し
  printf("%d+2と%d+2の和は%dです\n", a, b, c);
}

// 関数の定義(実体)
int add2(int x, int y) {
  int z;
  x += 2;  // xに2を加算
  y += 2;  // yに2を加算
  z = x + y;
  return z;  // zの値を戻り値として返す
}

リストリスト 2の実行結果は以下のとおりである.呼び出し側の変数abの値は変わっていないことが分かる.

5+2と10+2の和は19です

マクロ

マクロとは,本来,マクロ名で示される文字列を,他の文字列で置き換える機能を指す.C言語では,コンパイラが動作する前に,プリプロセッサと呼ばれるプログラムが動作する.マクロ機能は,プリプロセッサが持つ機能の一つであり,コンパイラは文字列が置換されたソースコードをコンパイルする.

単純なマクロ

単純な文字列置き換えのためのマクロの定義の方法は

#define	記号定数	文字列

である. ソースコード中の文字列PIを円周率として用いるためのマクロの定義 例は以下の通りである.

#define	PI 3.141592653589793

これ以降のソースコード中の文字列PIは,文字列3.141592653589793に置き換えられるので,あたかもPIという変数が円周率という定数を参照するために定義されているように用いることができる.

引数付きマクロ

仮引数を含むマクロ定義によって,比較的短い処理を関数定義を行うことなく記述することができる.

#define マクロ名 (仮引数の並び)

例えば,角度(°)をラジアンに変換する処理は以下のように書ける.

#define	rad(x) ((x) * PI / 180)

マクロ名と仮引数の並びの間にスペースを入れてはいけない.スペースが入るとこの場合,radという文字列が(x)という文字列に置換されるマクロになってしまう.また,仮引数が()で囲まれていることに注意.この括弧がない場合,思わぬ副作用が生じる可能性がある.例えば2つの整数を乗算するマクロとして,

#define	multiply(a, b) (a * b)

というマクロを定義したとする.3×7の計算結果を期待して

multiply(1 + 2, 3 + 4)

と使用すると,実際にはa → 1 + 2b → 3 + 4であるので

1 + 2 * 3 + 4

という意図しない計算が行われることになる.括弧をつけた定義

#define	multiply(a, b) ((a) * (b))

であれば,

(1 + 2) * (3 + 4)

と,意図する計算が実行される.

引数付きマクロでは3項演算子もよく用いられる.3項演算子は,条件によって代入される値を変えるための演算子であり,しばしばif文の代わりに使われる.3項演算の文法は以下の通りである.

条件 ? 真のときの値 : 偽のときの値

2つの整数のうち,大きい方を求めるマクロは,3項演算子を用いて以下のように書ける.

#define	MAX(a, b) (((a)>(b)) ? (a) : (b))