【C言語】引数なしの関数の定義(void無しは非推奨)

C言語における引数なしの関数の定義の仕方の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、C言語における “引数なし” の関数の定義の仕方について解説します。

他のプログラミング言語と同様に、C言語では開発者が自分自身で独自の関数を定義することができます。そして、関数は引数を受け取ることが可能で、関数呼び出し元から指定されたデータに応じた処理を行うことが出来るようになっています。

関数に引数を渡す様子

ただ、引数が不要な関数を作りたい場合もあります。そんな時、どうやって関数を定義すればよいでしょうか?この点について解説していきます。

仮引数に void を指定する

引数が不要な関数を定義する場合は仮引数に void を指定してやれば良いです。

例えば、下記は引数が不要な関数の定義例になります。

引数が不要な関数の定義
int func(void) {
    // 関数で実行したい処理
}

仮引数が void の関数は下記のように実引数なしで呼び出しすることが可能です。

実引数なしでの関数呼び出し
ret = func();

逆に、仮引数が void である関数を呼び出す際に実引数が指定された場合、コンパイル時に下記のようなエラーが発生することになります。つまり、その関数の呼び出し方が誤っていることをコンパイル時に検出することが可能です。

error: too many arguments to function ‘func’
   10 |     ret = func(10);

“仮引数なし” はやめた方が良い

実は、C言語では “仮引数なし” で関数を定義することも可能です。

例えば下記のように関数を定義したとしてもコンパイルに成功します。

仮引数なしの関数の定義
int func() {
    // 関数で実行したい処理
}

これでも引数なしの関数が実現できていそうに見えます。ですが、このような “仮引数なし” の関数定義はやめた方が良いです…。その理由は、関数の呼び出し方の間違いをコンパイル時にエラーとして検出できないからになります。

前述のとおり、仮引数が void の関数に実引数を指定して呼び出しするとコンパイル時にエラーが発生します。そして、これにより開発者は関数の呼び出し方が誤っていたことに気付くことが出来ます。気付けば修正することが出来ますね!

それに対し、”仮引数なし” の関数の場合、実引数を指定して呼び出しをしたとしてもコンパイル時にエラーは発生しません。例えば、先ほど定義した “仮引数なし” の関数を下記のように呼び出ししてもエラーは発生しません。

仮引数なしの関数の呼び出し
ret = func("abc", 'z', 12345, func);

実は、”仮引数なし” の関数は「どんな引数をいくつ指定しても良い関数」として扱われることになります。なので、上記のように実引数を指定して関数を呼び出してもコンパイル時にエラーが発生しません。

ただ、エラーが発生しないだけで実害はないように思えますね。現時点のソースコードにおいては確かに実害はないでしょう。ですが、今後のソースコードの変更によっては実害が発生する可能性があります。重大なバグを生み出す可能性があります。なので、上記のような仮引数なしの関数の定義はやめた方が良いです。

スポンサーリンク

”仮引数なし” での関数定義によって発生する不具合の例

どんな実害が発生する可能性があるのか、具体例を用いて説明したいと思います。

例えばプログラムの開発者に A さんと B さんがいて、2人が共同で1つのプログラムを開発しているとしましょう。

実害が発生する例におけるプログラムの開発体制

ここで A さんは、引数なしの関数を定義しようと思って下記のように関数を定義したとします。この関数はグローバル変数 a1.1 倍の値を算出する関数になります。 

func関数の定義
int func() {
    return (int)(a * 1.1);
}

そして、Bさんも偶然グローバル変数 a1.1 倍の値を求める関数を利用したかったので、上記の func 関数を見つけて func 関数を呼び出す処理を下記のように記述したとします。この時、B さんは下記のように誤って関数 func に関係のない変数 x を実引数として指定してしまったとします。

func関数の呼び出し(再掲)
int x;
int ret;

// 略

ret = func(x);

// retが"a * 1.1"の値であることを期待した処理

ですが、関数 func は仮引数なしで定義されているため、間違って実引数を指定したとしても、この時点ではエラーが発生しません。また、関数 func はグローバル変数 a1.1 倍の値を返却する関数ですので、最後の retが"a * 1.1"の値であることを期待した処理 も期待通りに動作することになります。

Bさんが誤って実引数を指定する様子

次の日、開発するプログラムの仕様の変更が必要になりました。そのため、A さんは昨日定義した関数 func を下記のように変更したとします。変更後の関数 func では、グローバル変数ではなく引数で受け取った y1.1 倍の値を返却する関数となっています。

func関数の変更
int func(int y) {
    return (int)(y * 1.1);
}

このように関数の定義を変更したとしても、たまたま B さんが間違って int 型の変数 xfunc 関数の実引数に指定しているため、コンパイルは今まで通りエラーも出ずに正常に終了することになります。

Aさんが関数funcの引数を変更する様子

ですが、B さんはあくまでも func 関数がグローバル変数 a1.1 倍の値を返却することを期待して func 関数を呼び出す処理や呼び出した後の処理を記述しています。ですが、func 関数の定義が変わってしまい、引数で指定した値の 1.1 倍の値を返却するようになってしまったため、func 関数呼び出し後の retが"a * 1.1"の値であることを期待した処理 意図通りに動作しなくなります。

func関数の呼び出し(再掲)
int x;
int ret;

// 略

ret = func(x);

// retが"a * 1.1"の値であることを期待した処理

これによって、どういった不具合が発生するかはプログラム全体の作りによって異なりますが、少なくとも retが"a * 1.1"の値であることを期待した処理 の部分が意図通りに動かなくなるため、何らかの不具合を生み出すことにはなるでしょう。

関数の引数の変化によって関数の返却値が今迄から変わってしまった様子

なので、不具合の原因調査が必要になり、もしかしたら原因調査に時間がかかってプログラムのリリース時期を遅らせることになり、それによってビジネスチャンスを逃してしまうような可能性もあります。今回の場合は func 関数のみ変更しているので原因特定が容易かもしれませんが、ソースコードの規模が大きくなると原因調査はそれだけ困難になります。

バグの修正に難航している様子

仮引数を void にすることで不具合の可能性に事前に気付くことが可能

勝手に関数を変更するなよ!など、いろいろ突っ込みどころのある例になってしまったかもしれませんが、引数が不要な関数を “仮引数なし” で定義すると、当初は問題がなくても、後で変更した際に問題が生じる可能性があることを、少しでも理解していただけたかと思います。

先ほどの例で不具合が発生してしまった原因はたくさん挙げられると思いますが、ここでは “func 関数の定義の仕方” に注目したいと思います。引数が不要なのに仮引数を void にしなかった点です。

もし A さんが仮引数を void として func 関数を定義していれば、次に B さんが誤って実引数を指定して func 関数を呼び出す処理を記述した時点でコンパイルエラーが発生し、即座に func 関数の使い方が誤っていることに気付くことが出来ます。

コンパイルエラーが発生して引数の指定の仕方が間違っていることに気付けた様子

そして、その時点で修正していれば、次に A さんが func 関数の定義を変更した際には func 関数呼び出し部分で再びコンパイルエラーが発生するようになり、その時点で関数の定義を変更することで B さんが実装した部分にも影響があることに気付くことができます。

関数の定義変更時にコンパイルエラーの発生によって変更の影響箇所に気付けるようす

つまり、引数なしの関数を定義する際に、仮引数を void にしておけば間違った呼び出し方をされたタイミングでコンパイルエラーが発生し、すぐに間違いに気付くことができたのです。そして、不具合の発生を未然に防止することができたはずです。

ですが、引数なしの関数を定義する際に void を省略して “仮引数なし” にしてしまったため、誤った呼び出し方をされてもエラーが発生せず、コンパイルに成功してしまいました。これにより、関数の使い方の間違いに気付くことができず、それが後々の不具合の発生につながってしまったことになります。

この例のように、後々の重大な不具合を発生させないためにも、関数の使い方に間違いがあることに気付けるようにしておくことが重要です。今回の場合は、引数なしの関数に対して実引数を指定して関数を呼び出すことで不具合が発生しているため、そのことに気付けるようにしておけば、不具合を未然に防ぐことができたと考えられます。より具体的には、引数なしの関数の仮引数を void にしておけば、間違って実引数を指定して呼び出した際にコンパイルエラーが発生してコンパイル自体が行えないようになり、その時点で関数の使い方の誤りに気付いて修正することが出来たことになります。

こういった不具合の未然防止のためにも、引数なしの関数を定義する際には必ず仮引数を void にするのが良いです。

ちょっとした修飾子が開発効率向上の仕組みとなる

また、仮引数を void にすることは、その関数が関数定義者の意図通りに呼び出しされているかどうかをチェックする仕組みにもなります。前述のとおり、仮引数を void にしておけば、その関数に実引数を指定して呼び出しするとコンパイル時にチェックが行われてエラーになるようになります。このような、関数の使い方が関数定義者の意図するものと合っているかどうかをチェックする仕組みを適切に利用することで、例えば上記の例の不具合も未然に防ぐことが可能となります。

C言語には、関数の使い方が関数定義者の意図するものと合っていることをチェックする仕組みが多く存在し、こここまで紹介してきた void もそうですし、conststatic 等も、この “意図通りに関数や変数が利用されることをチェックする仕組み” を利用するための修飾子として挙げられます。

例えば、static 修飾子が付けられている関数は他のファイルから呼び出すことが出来ません。つまり、定義する関数を他のファイルから呼び出しされたくないのであれば、関数に static 修飾子を付けておくことで、そのような呼び出しをコンパイル(リンク)の時にチェックすることが出来るようになります。そして、そのチェックに引っかかればエラーとなります。したがって、必ず開発者の「この関数は他のファイルから呼び出しされたくない」という意図通りにプログラムの開発が行われることになります。

static修飾子が実装者の意図しない関数利用を防ぐ仕組みとなる様子

前述の例も、func 関数の定義者の意図通りに関数が呼び出しされなかったことが不具合発生の引き金になっています。そして、そういった意図通りに開発が行われているかをチェックできていれば、不具合を未然に防ぐことが出来たと考えられます。このように、自身の開発した関数(だけでなくモジュールなども)が意図通りに利用されているかどうかは、特に複数人のプログラム開発をスムーズに進めていくうえで重要です。逆に、意図に反する使い方をされたりすると、それが不具合の原因になります。

何気なく利用していたり、無くてもいいから利用していなかった修飾子も、上手く利用すれば、こういったチェックの仕組みの導入につながって開発がスムーズに進むようになり開発効率が向上しますので、ぜひ今後は積極的に利用するようにしてみてください!ちなみに、conststatic については下記ページで解説していますので、興味が出てきた方はぜひ下記も読んでみていただければと思います。

constの解説ページアイキャッチ 【C言語】constの使い方・メリットを解説 static解説ページのアイキャッチ static 関数と static グローバル変数の使い方・メリットを解説

スポンサーリンク

まとめ

このページでは、C言語における “引数なしの関数” の定義の仕方について解説しました!

C言語では、引数なしの関数は仮引数を void にすることで実現できます。仮引数を void にしておくことで、実引数を指定して関数呼び出しされた際にはコンパイルエラーが発生するようになります。

“仮引数なし” で関数を定義しても引数なしの関数が実現できそうなものではあるのですが、逆に何でも引数を指定しても良いことになってしまい、呼び出し時に実引数を指定してもコンパイルエラーが発生しないので注意してください。引数なしの関数を定義しようと思って “仮引数なし” で関数を定義した場合、関数が誤った使い方をされたとしてもコンパイルエラーが発生せず、そのことに開発者は気付くことが出来ません。そして、それが後々の不具合等につながる可能性がありますので、明示的に引数なしであることを示し、さらに誤った使い方をされた際にコンパイルエラーが発生するように、引数なしの関数を定義する際には仮引数を void にするようにしましょう!

また、今回紹介した void に似た型として void* が存在します。見た目は似ていますが、この voidvoid* は全く異なるものなので注意してください。void* に関しては下記ページで解説していますので、是非こちらも読んでみていただければと思います!

voidとvoid*型の解説ページのアイキャッチ 【C言語】void型とvoid*型(void型ポインタ)について解説

オススメの参考書(PR)

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示