C言語のあれこれ4(関数の作成方法)

に公開
2

はじめに

以下の記事の続きになります。

C言語のあれこれ1(Hello Worldの解読)

C言語のあれこれ2(変数と定数の作成方法)

C言語のあれこれ3(基本的な値型の種類と構造体の作成方法)

以下の順番で作成していきます。

追記

前回の指摘を受けたことで、学校で習ったものと習っていない物の差を痛感しました。
やはり最低限一度は自分でも調べてみたほうが良いと実感しました。

記事を作成する時間もできたので、投稿を再開するため、
以降もおかしな点があれば指摘していただけると幸いです。

追記2

2025年の2月ごろに会社でFlutterの開発準備から開発方法を教える機会があったため、こちらの記事も頑張って作成します。

応援よろしくお願いします。

本編

ここでは関数の作成方法に関してを記述します

関数の内容

関数は基本的に次の内容で構成されます。

戻り値の型 関数名(引数の型 引数名, 引数の型 引数名...)

引数の型と引数名を合わせて1つの引数を指定できます。

戻り値の型には値型や構造体を指定できますが、戻り値を利用しない場合はvoidを指定します。

関数から抜ける場合はreturn文を利用します。
この時、戻り値の型がvoid以外の場合はreturnを必ずつける必要があり、戻り値の型と同じ型の値を返す必要があります。

以下は関数の定義になります。

void Test1()
{
    return;
}

int Test2(int test)
{
    return test * test;
}

プロトタイプ宣言

プロトタイプ宣言は先にこの関数があることを伝えるために記述します。
プロトタイプ宣言は複数定義することができます。

関数内で利用する関数は先にその関数があることを伝える必要があります。
そのため関数の定義としてプロトタイプ宣言と呼ばれるものを行います。

関数の内容を記述する場所は一か所のですが、プロトタイプ宣言は何回も記述することができます。

int Test3();//プロトタイプ宣言したTest3関数//

main関数

エントリーポイント(プログラムの開始地点)になります。
C言語では基本的にmain関数がエントリーポイントに当たります。

Windowsのデスクトップアプリケーションを開発している人は基本的にWinMainエントリーポイントになるため、注意してください。

int main()
{
    Test1();
    int test = Test2(5);
    int test2;
    test2 = Test3();

    return 0;
}

※1.C言語はmain関数の返す値でアプリケーションの正異常判定を行います。
そのため、戻り値が0の場合は正常終了、0以外の場合は異常終了と認識されます。

Test2(5)と関数を利用している部分ですが、Test2の中で利用するint testと引数で渡した5の値は別の変数になります。
動きとしては、Test2の開始時に新しい変数を用意して5を代入する'になります。 これを値渡しと言います。 値渡し`ではTest2に引数として渡されたものが変数の場合でも、その変数の値が変わることはありません。

しかし、ポインタを渡す場合は話が変わります。
ポインタを渡す場合はポインタ渡しと呼ばれます。
ポインタ渡しではポインタ自体は値渡しですが、ポインタの指す先は同じものになるため、関数内でポインタ先の値を変動すると、関数で使用したポインタ先の値も変動されます。

最後にすべての内容を結合して動かすコードにすると、以下の通りになります。


void Test1()
{
    return;
}

int Test2(int test)
{
    return test * test;
}

int Test3();//プロトタイプ宣言したTest3関数//

int main()
{
    Test1();
    int test = Test2(5);
    int test2;
    test2 = Test3();

    return 0;
}


int Test3()
{
    return 500;
}

Discussion

齊藤敦志齊藤敦志

タグに C++ も入っていますが C と C++ は規則がかなり違います。 また、規格の改定もあるので資料を見るときはどの版のことを想定しているのかを意識しないと混乱することもあります。

int Test3();//プロトタイプ宣言したTest3関数//

C ではこの Test3プロトタイプ (関数原型) のない関数宣言として解釈されます。 プロトタイプがないというのは型情報がない扱いであるという意味です。

型情報がないのでコンパイラが型チェックを充分に行わないことがあり、しかし型の不整合があって良いわけではないので間違いがあるとコンパイラが検出しないままデタラメな動作をするかもしれません。

仮引数リストが空の場合はそういう解釈になるので引数を受け取らない関数は

int Test3(void);

というように void を書く必要があります。

C++ ではプロトタイプのない関数宣言という概念はなく、 void を書いても書かなくても単に仮引数を受け取らないプロトタイプ宣言として解釈されます。 また、 C でも 2023 年の改定からは C++ と同様の解釈になるように変更されたのですが、現時点でそれをあてにするのは時期尚早だと思います。

戻り値の型がvoid以外の場合はreturnを必ずつける必要があり

原則としてはそうすべきであるのは間違いないのですが、 C では return を通過せずに } に到達して、なおかつ関数の呼び出し元で返却値を使うことが未定義であるということになっています。 逆に言えば return がないだけなら一応は有りです。 あえてそんなことをする理由もないので豆知識というか与太話レベルのことですが。

ちなみに C++ では return を通過せずに } に到達したらそれだけでただちに未定義です。

戻り値の型と同じ型の値を返す必要があります

関数の返却値の型が return に渡される式の型と一致しない場合は代入時と同じ規則で暗黙の型変換が試みられます。 暗黙の型変換が可能であればそのように型が調整されるので型が完全に一致しなければならないというわけではないです。

戻り値が0の場合は正常終了、0以外の場合は異常終了と認識されます。

0EXIT_SUCCESS の場合は成功、 EXIT_FAILURE の場合は失敗、それ以外は処理系定義であるということになっています。

詳細はホスト環境 (OS) の都合によるのでどんな値でも構わないというわけではないです。 たとえば POSIX では下位 8 ビットのみがホスト環境に渡されるので大きな値を返せません。

Chronoss0518Chronoss0518

早速コメントありがとうございます。

C ではこの Test3 はプロトタイプ (関数原型) のない関数宣言として解釈されます。 プロトタイプ>がないというのは型情報がない扱いであるという意味です。

型情報がないのでコンパイラが型チェックを充分に行わないことがあり、しかし型の不整合があって良いわけではないので間違いがあるとコンパイラが検出しないままデタラメな動作をするかもしれません。

仮引数リストが空の場合はそういう解釈になるので引数を受け取らない関数は
int Test3(void);
C ではこの Test3 はプロトタイプ (関数原型) のない関数宣言として解釈されます。 プロトタイプがないというのは型情報がない扱いであるという意味です。

型情報がないのでコンパイラが型チェックを充分に行わないことがあり、しかし型の不整合があって良いわけではないので間違いがあるとコンパイラが検出しないままデタラメな動作をするかもしれません。

仮引数リストが空の場合はそういう解釈になるので引数を受け取らない関数は

特にこちらはVisual C/C++を利用しているとわからない内容ですね。
ありがとうございます。

また、

0 か EXIT_SUCCESS の場合は成功、 EXIT_FAILURE の場合は失敗、それ以外は処理系定義であるということになっています。

詳細はホスト環境 (OS) の都合によるのでどんな値でも構わないというわけではないです。 たとえば POSIX では下位 8 ビットのみがホスト環境に渡されるので大きな値を返せません。

こちらに関しては完全に初見でした。
記事も見つけたため、以下に添付して覚えておきます。
https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/exit-success-exit-failure?view=msvc-170

改めてコメントしていただきありがとうございます。