Compaq Cプログラムは,ユーザ定義関数とシステム定義関数の集合です。 関数を使用すると大規模なタスクを小さく分割できるため,理解しやすく, かつ維持しやすいモジュール・プログラムを設計する際に役に立ちます。 関数は,呼出し時に実行する0個以上の文を含みます。また,関数には0 個以上の実引数を渡すこともでき,1つの関数から1つの値を返すことができます。
この章では,Compaq Cの関数について説明します。
関数呼出しは1次式であり,通常は括弧が続く関数識別子です。この識別子は関数を呼び出すために使用します。
括弧の中には,コンマで区切られた式並びが含まれます。
これは関数の実引数であり,空の場合もあります。
次に,power
関数の呼出しの例を示します。この関数はすでに適切に定義されているものと仮定します。
main() { . . . y = power(x,n); /* function call */ }
関数呼出しについては,第6.3.2 項を参照してください。
関数には,派生型である「型を返す関数」があります。
この型を配列型または関数型以外のデータ型にすることはできます。
配列や関数へのポインタを返すこともできます。
関数が値を返さない場合,この型は「void
を返す関数」になり,void
関数と呼ばれます。Compaq Cの void
関数はPascal
言語における手続き,またはFORTRANにおけるサブルーチンと同じものです。
Compaq Cの void
関数以外の関数は,
他の言語で使用する関数と同じです。
関数は,次のいずれかの方法でプログラムに使用することができます。
power
は
int
を返す関数です。
int power(int base, int exp) { int n=1; if (exp < 0) { printf ("Error: Cannot handle negative exponent\n"); return -1; } for ( ; exp; exp--) n = base * n; return n; }
関数定義については,第5.3節を参照してください。
main
関数が power
関数を宣言し,
呼び出しています。関数定義,つまりコードが定義されている場所は別に存在します。
main() { int power(int base, int exp); /* function declaration */ int x, n, y; . . . y = power(x,n); /* function call */ }
仮引数が仮引数型並びに宣言される関数定義の形式は,関数プロトタイプ と呼ばれます。関数プロトタイプでは,コンパイラは関数の実引数が各仮引数と一致するかどうかを検査し, その実引数を仮引数の宣言された型に変換します。
関数定義には関数のコードが含まれます。関数定義はどのような順序でも記述できます。 また,1つの関数を複数のファイルに分割することはできませんが,1 つまたは複数のソース・ファイルのいずれにでも記述できます。 関数定義はネストすることはできません。
関数定義の構文は次のとおりです。
宣言指定子(opt) 宣言子 宣言並び(opt) 複文
記憶域クラス指定子は特に指定しない限り,
extern
となります。static
指定子を使用することもできます。
記憶域クラス指定子については,第2.10節を参照してください。
ANSI規格では,型修飾子を const
または volatile
に指定することもできますが,
関数の返す型にこの修飾子を使用しても意味がありません。
これは,関数は右辺値のみを返し,型修飾子は左辺値のみに適用されるからです。
型指定子は関数が返す値のデータ型です。返却値の型が指定されていない場合,
その関数は int
型の値を返すものとして宣言されます。
関数は,「型の配列」または「
型を返す関数」以外のどのような型の値でも返すことができます。
配列と関数へのポインタを返すことができます。返却値がある場合には,
return
文の式に指定します。return
文を実行すると関数の実行が終了し,
呼出し関数に制御が返されます。値を返す関数では,
関数の返す型と互換性のある型を持つ式を次に示す書式で
return
の後に続けることができます。
return 式 ;
必要に応じて,式は関数の返す型へ変換されます。関数が返す値は左辺値ではないことに注意してください。 したがって,関数呼出しは代入演算子の左側に置くことはできません。
次に,文字を返す関数定義の例を示します。
char letter(char param1) { . . . return param1; }
呼出し関数は返却値を無視できます。return
の後に式が指定されない場合,
または右中括弧によって関数が終了する場合の返却値は定義されていません。
void
関数の場合には値は返されません。
関数が値を返さない場合,または関数が値を必要としない状況から常に呼び出される場合には,
返却値の型として void
を指定する必要があります。
void message() { printf("This function has no return value."); return; }
以下の場合に関数定義または関数宣言において返却値の
void
型を指定すると,エラーが発生します。
return
文でエラーが生じます。
void
関数が呼び出されると,
関数呼出し側でエラーが生じます。
f1
に示すように,宣言子は単一識別子のように単純な形式で指定できます。
int f1(char p2)
次の例では,f1
は「int
を返す関数」です。また,次の例のように宣言子を複雑な構文にすることもできます。
int (*(*fpapfi(int x))[5])(float)
この例では,fpapfi
は「int
を返す(
float
実引数をとる)関数を指示する5つのポインタの配列に,
さらにポインタを返す(int
実引数をとる)関数」です。
特定の宣言子の構文については,第4
章を参照してください。
宣言子(関数)はあらかじめ宣言しておく必要はありません。関数があらかじめ宣言された場合には, その関数定義の仮引数型と返却値の型は前の関数宣言と等しくなければなりません。
宣言子には,関数の仮引数並びを含めることができます。Compaq Cでは,
最大で253個の仮引数をコンマで区切って表した並びを括弧で囲んで指定することができます。
各仮引数は特に指定しない限り
auto
記憶域クラスになりますが,register
も使用できます。仮引数並びの右括弧の後にセミコロンは付けません。
関数の仮引数を指定する方法には,次の2種類があります。
これは仮引数型並びを含んでいます。次にその例を示します。
int f1(char a, int b) { function body }
これは識別子並びを含んでいます。仮引数型は関数定義内の別の 宣言並びに定義します。この宣言並びは関数本体を開始する左中括弧の前に指定します。 次にその例を示します。
int f1(a, b) char a; int b; { function body }
宣言されない仮引数は int
型とみなされます。
仮引数を持たない関数定義は空の仮引数並びで定義されます。この空の仮引数並びは, 次の2つの方法のいずれかで指定されます。
void
キーワードを使用します。次にその例を示します。
char msg(void) { return 'a'; }
char msg() { return 'a'; }
プロトタイプ形式を使用して定義される関数は,その関数のプロトタイプを設定します。 プロトタイプは,同じ関数の先行する宣言または後に続く宣言と一致しなければなりません。
旧形式を使用して定義される関数は,プロトタイプを設定しません。しかし, その関数の以前の宣言によるプロトタイプが存在する場合には,省略時の仮引数が定義の仮引数として適用され, その定義の仮引数宣言がプロトタイプの各仮引数宣言と厳密に一致していなければなりません。
1つの関数に,旧形式とプロトタイプ形式の宣言と定義を混用しないでください。 使用することはできますが推奨できません。
return
文を含めることはできますが,必須ではありません。
関数の返却値が int
の場合には,関数を宣言せずに呼び出すことができます。
ただし,この方法では型検査が行われないため推奨できません。
すべての関数を宣言してください。返却値が別の型の場合で,
関数定義がソース・コード中の呼出し関数の後に指定される場合には,
その関数はそれを呼び出す前に宣言する必要があります。次にその例を示します。
char lower(int c); /* Function declaration */ caller() /* Calling function */ { int c; char c_out; . . . c_out = lower(c); /* Function call */ } char lower(int c_up) /* Function definition */ { . . . }
ソース・コード中で caller
関数の前に
lower
の関数定義が位置している場合には,
lower
を呼び出す前に再度宣言する必要はありません。
この場合,関数定義はそれ自体の宣言として機能し,同じソース・ファイル中でその後に定義されるすべての関数からの関数呼出しのスコープに入っています。
lower
の関数定義と関数宣言はいずれも,プロトタイプ形式であることに注意してください。
Compaq Cでは,仮引数型を関数宣言子に指定しない旧形式の関数宣言をサポートします。
ただし,効率のよいプログラミング方法としては,
プログラム中のすべてのユーザ定義関数にプロトタイプ宣言を使用して,
関数を使用する前に各プロトタイプを指定します。
さらに,関数宣言の仮引数識別子は,関数定義の仮引数識別子とは異なっていても有効であることに注意してください。
関数宣言では,void
キーワードを使用して,空の実引数並びを指定しなければなりません。
次にその例を示します。
char function_name(void);
関数定義では,void
キーワードを関数宣言に使用して,値を返さない関数の返却値の型を指定することもできます。
次にその例を示します。
main() { void function_name( ); . . . } void function_name( ) { }
関数プロトタイプは,仮引数並びに実引数のデータ型を指定する関数宣言です。 コンパイラは関数プロトタイプの情報を使用して,プロトタイプのスコープ内の対応する関数定義, および対応するすべての関数宣言と関数呼出しに正しい数の実引数や仮引数が含まれていることを確認し, その各実引数や仮引数が正しいデータ型のものであることを確認します。
プロトタイプは,旧形式の関数宣言とは構文的に区別されます。これら2 つの形式は単一の関数で混用できますが,推奨できません。次は,旧形式の宣言とプロトタイプ形式の宣言を比較したものです。
旧形式:
プロトタイプ形式:
void
キーワードを使用して指定します。
関数プロトタイプの構文は,次のとおりです。
宣言指定子(opt) 宣言子;
宣言子は仮引数型並びを含んでおり, この並びがその関数の仮引数の型の指定と識別子の宣言を行います。
仮引数型並びをvoid
型の単一仮引数で構成すると,
関数には仮引数がないという指定になります。
仮引数型並びは,[*]
という表記で指定した可変長配列であるメンバを含むことができます。
最も簡単な形式では,関数プロトタイプ宣言は 次の書式になります。
記憶域クラス(opt) 返す型(opt) 関数名(型(1) 仮引数(1), ...,型(n) 仮引数(n));
次の関数定義を例にとります。
char function_name( int lower, int *upper, char (*func)(), double y ) { }
次は,この関数に対応するプロトタイプ宣言です。
char function_name( int lower, int *upper, char (*func)(), double y );
プロトタイプはプロトタイプ形式に指定した対応する関数定義のヘッダと同一であり, 必要に応じて終了のセミコロン( ; ) またはコンマ( , )を付けます。それは,そのプロトタイプが単独で宣言されているか, 複数宣言中のものであるかによって異なります。
関数プロトタイプは,それに対応する関数定義と同じ仮引数識別子を使用する必要はありません。 これは,プロトタイプの識別子が識別子並び内のみのスコープを持つためです。 さらに,識別子自体はプロトタイプ宣言に指定する必要はなく, その型だけが必要です。
たとえば,次のプロトタイプ宣言はすべて等しいものです。
char function_name( int lower, int *upper, char (*func)(), double y ); char function_name( int a, int *b, char (*c)(), double d ); char function_name( int, int *, char (*)(), double );
プログラムの明確さを向上し,コンパイラの型検査能力を高めるためには, できるだけ識別子をプロトタイプに含めるようにしてください。
可変個の実引数並びは,関数プロトタイプに省略記号を含めることによって指定します。 最低1つの仮引数を,省略記号の前に指定しなければなりません。 次にその例を示します。
char function_name( int lower, ... );
データ型の指定は,関数プロトタイプから省略することはできません。
C99規格では,関数のプロトタイプ宣言での仮パラメータの最も外側の
配列境界で,static
キーワードを使用できます。
このキーワードを使用すると,関数の呼び出しごとに,
少なくとも配列境界の宣言で指定した配列要素と同じ数だけ,
対応する実引数がアクセス可能であることをコンパイラに知らせることができます。
たとえば,次の2つの関数定義があるとします。
void foo(int a[1000]){ ... } void bar(int b[static 1000]) { ...}
この foo
の宣言は,a
を
int *
として宣言するのとまったく同じです。
foo
の本体をコンパイルするとき,配列要素がいくつあるか
については,コンパイラは何もわかりません。
bar
の宣言は,少なくとも1000個の配列要素が存在し,
安全にアクセス可能であることをコンパイラが前提とできる点が
異なります。
この目的は,安全にプリフェッチできる配列要素の範囲をオプティマイザに
知らせることです。
プロトタイプは,プログラムの各コンパイル単位に適切に配置しなければなりません。 プロトタイプの位置は,そのスコープを決定します。関数プロトタイプがそれに対応する関数呼出しのスコープ内とみなされるのは他の関数宣言の場合と同様に, プロトタイプが関数呼出しと同じブロック内で指定される場合, ネストされたブロック内で指定される場合,またはソース・ ファイルの一番外側のレベルで指定される場合のみです。コンパイラは, プロトタイプの位置からそのスコープの終わりまですべての関数定義, 宣言,および呼出しを検査します。プロトタイプの位置を間違えて配置し, 関数定義,宣言,または呼出しがプロトタイプのスコープ外で行われた場合には, その関数呼出しはプロトタイプがないものとして処理されます。
関数プロトタイプの構文は各関数定義の関数ヘッダを抽出し,セミコロン(
; )を追加し,ヘッダに各プロトタイプを配置し,
プログラム中の各コンパイル単位の先頭にそのヘッダを含めることを可能にするために設計されています。
この方法によって関数プロトタイプは外部宣言され,
そのプロトタイプのスコープはコンパイル単位全体に拡張されます。
Compaq Cライブラリ関数呼出しのプロトタイプ検査を使用するには,
プログラムが使用するライブラリ関数を含む
.h
ファイルを呼び出すための
#include
前処理命令を指定します。
関数定義,宣言,または呼出しの各実引数の数がプロトタイプと一致しない場合には, エラーが発生します。
関数呼出しの実引数のデータ型が関数プロトタイプの対応する型と一致しない場合, コンパイラは変換を試みます。一致しない実引数がプロトタイプ仮引数と代入互換性がある場合, コンパイラはその実引数を実引数の変換の規則( 第5.6.1項を参照)によって, プロトタイプに指定されているデータ型に変換します。
不一致の実引数とプロトタイプ仮引数の間に代入互換性がない場合は,エラー・ メッセージが出されます。
C関数は,仮引数と実引数を通じて情報を交換します。仮引数とは, 関数宣言または定義において関数名の後に続く括弧内の宣言のことです。 実引数とは関数呼出しの括弧内の式のことです。
次に,C関数の仮引数と実引数に適用される規則を示します。
int
型が割り当てられる。
関数呼出しでは,評価される実引数の型はそれに対応する仮引数の型と一致しなければなりません。 一致しない場合には,次の変換が行われます。 この方法は,プロトタイプが関数のスコープ内にあるかどうかによって異なります。
void f(char, short, float, ...); char c1, c2; short s1,s2; float f1,f2; f(c1, s1, f1, c2, s2, f2);
c1
,s1
,および
f1
実引数はそれぞれの型で渡されます。これに対して,
c2
,
s2
,f2
実引数は,それぞれ
int
,int
,double
に変換されます。
float
型の実引数は
double
に変換される。
char
,unsigned
char
,short
,および
unsigned short
型の実引数は
int
に変換される。
unsigned
char
または unsigned
short
型の実引数を unsigned
int
型に変換する。
実引数では,これ以外の省略時の変換は行われません。特定の実引数をそれに対応する仮引数の型と一致させるために変換が必要な場合には, キャスト演算子を使用します。 キャスト演算子については,第6.4.6項を参照してください。
関数識別子と配列識別子は,関数の実引数として指定できます。関数識別子は括弧なしで指定し, 配列識別子は大括弧なしで指定します。関数識別子または配列識別子が指定されると, その関数または配列のアドレスとして評価されます。 さらに,その返却値が整数であっても,その関数を宣言または定義する必要があります。 例 5-1では, 実引数として渡す関数の宣言の方法と時期,および渡し方を示しています。
int x() { return 25; } /* Function definition and */ 【1】 int z[10]; /* array defined before use */ fn(int f1(), int (*f2)(), int a1[])) /* Function definition */ 【2】 { f1(); /* Call to function f1 */ . . . } void caller(void) { int y(); /* Function declaration */ 【3】 . . . fn(x, y, z); /* Function call: functions */ 【4】 /* x and y, and array z */ /* passed as addresses */ . . . } int y(void) { return 30; } /* Function definition */
次は,例 5-1の説明です。
x
関数は別の場所で宣言することなく,実引数並びに渡すことができます。
これは,caller
関数の前にある定義がその宣言として機能するからです。
fn(int f1(), int f2(), int a1[]) /* f1, f2 declared as */ {...} /* functions; a1 declared */ /* as array of int. */ fn(int (*f1)(), int (*f2)(), int *a1) /* f1, f2 declared as */ {...} /* pointers to functions; */ /* a1 declared as pointer */ /* to int. */
このような仮引数が関数または配列として宣言されると,コンパイラはそれに対応する実引数を自動的にポインタへ変換します。
caller
関数の後に位置するため,
y
関数は実引数並びに渡される前に宣言しなければなりません。
プログラムの起動時に呼び出される関数を main
と呼びます。main
関数は仮引数なしで,
または2つの仮引数を指定して定義できます。この2つの仮引数は,
main
関数の実行開始時にコマンド行実引数をプログラムに渡すためのものです。2
つの仮引数は,ここでは
argc および argv
という名前で説明しますが,それらを宣言する関数の中でのみ有効であるため任意の名前を使用することもできます。
main
関数の構文は次のとおりです。
int main { . . . }
int main(int argc, char *argv[ ]){ . . . }
argv[argc]
値は空ポインタです。
argc の値が0よりも大きい場合,配列メンバの argv[0] から argv[argc-1] までは文字列へのポインタを含みます。 これには,プログラムの起動前のホスト環境による処理系定義値が与えられています。 この目的は,ホスト環境以外の場所からプログラムの起動が行われる前に判別された情報をそのプログラムに提供することにあります。 たとえば,ホスト環境が大小文字の両方を含む文字列を提供できない場合には, そのホスト環境は文字列を小文字で受け取ることを保証します。
argc の値が0よりも大きい場合,argv[0] で示される文字列はプログラム名を表します。プログラム名がホスト環境から使用できない場合, argv[0] はヌル文字です。 argc値が1よりも大きい場合,argv[1] から argv[argc-1] で示される文字列はプログラムの仮引数を表します。
argc および argv仮引数と argv配列で示される文字列は,プログラムによって変更できます。 最後に格納された値は,プログラムの起動から終了までの間だけ保持されます。
main
関数定義における仮引数はオプションです。ただし,
アクセスできる仮引数は定義した仮引数のみです。
main
関数との実引数の引渡しについては,プラットフォームに固有の
Compaq Cのマニュアルを参照してください。