概要
C++では、返り値の型がvoid
でない関数からreturn
文を使わずに戻ろうとしてはいけません。
なぜいけないか
C++では、返り値の型がvoid
でない関数の実行がreturn
文を通らずに最後に到達すると、未定義動作になります。
未定義動作になると何が起こってもおかしくありません。
C言語では?
C言語では、返り値の型がvoid
でない関数からreturn
文を使わずに戻ろうとしただけでは未定義動作にはなりません。
ただし、その戻り値を使うと未定義動作になります。
根拠は?
C言語では
C言語の規格書に近いとされるN1570 6.9.1 Function definitions の 12 に、以下の記述があります。
If the } that terminates a function is reached, and the value of the function call is used by
the caller, the behavior is undefined.
関数最後の}
に到達し(すなわち、return
文をつかわずに戻り)、
かつその返り値が呼び出し手によって使われると未定義動作になる、とされています。
C++では
C++の規格書に近いとされるN3337 6.6.3 The return statement の 2 に、以下の記述があります。
Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.
関数の最後まで実行する(すなわち、return
文をつかわずに戻ろうとする)と、
値を返す関数(すなわち、返り値の型がvoid
でない関数)では未定義動作になる、とされています。
実際のコンパイル例
Compiler Explorer を用いて以下のコードをコンパイルしてみます。
hoge
は返り値の型がvoid
でなくreturn
文が無い関数(危険)、
fuga
は返り値の型がvoid
である関数、foo
は返り値の型がvoid
ではなくreturn
文がある関数です。
#include <stdio.h>
int hoge(int a) {
printf("hoge %d\n", a);
}
void fuga(int a) {
printf("fuga %d\n", a);
}
int foo(int a) {
printf("foo %d\n", a);
return 0;
}
C言語の場合
関数hoge
、fuga
では別の関数をjmp
で呼び出すことでその関数からの戻りを利用して戻り、
関数foo
ではret
命令を用いて戻っています。
これだけでは、危険は無いようです。
.LC0:
.string "hoge %d\n"
hoge:
movl %edi, %esi
xorl %eax, %eax
movl $.LC0, %edi
jmp printf
.LC1:
.string "fuga %d\n"
fuga:
movl %edi, %esi
xorl %eax, %eax
movl $.LC1, %edi
jmp printf
.LC2:
.string "foo %d\n"
foo:
subq $8, %rsp
movl %edi, %esi
xorl %eax, %eax
movl $.LC2, %edi
call printf
xorl %eax, %eax
addq $8, %rsp
ret
https://gcc.godbolt.org/z/fWKToh
C++の場合
関数fuga
では別の関数をjmp
で呼び出すことでその関数からの戻りを利用して戻り、
関数foo
ではret
命令を用いて戻っています。
一方、この例においては、未定義動作である関数hoge
には関数から戻る命令が無く、
その次にある文字列を機械語命令として実行しに行ってしまいます。
その結果、デタラメな実行結果になったり、不正な命令があったとして強制終了したりする可能性があります。
.LC0:
.string "hoge %d\n"
hoge(int):
movl %edi, %esi
subq $8, %rsp
movl $.LC0, %edi
xorl %eax, %eax
call printf
.LC1:
.string "fuga %d\n"
fuga(int):
movl %edi, %esi
xorl %eax, %eax
movl $.LC1, %edi
jmp printf
.LC2:
.string "foo %d\n"
foo(int):
subq $8, %rsp
movl %edi, %esi
xorl %eax, %eax
movl $.LC2, %edi
call printf
xorl %eax, %eax
addq $8, %rsp
ret
https://gcc.godbolt.org/z/hqcaoK
結論
C言語ではreturn
文を使わずに戻るだけでは未定義動作にならないとはいえ、
せっかく値を返すように宣言されているのにその値を全く使わない、というのはおかしいです。
また、C++ではreturn
文を使わずに戻るだけで不正動作の原因になり得ます。
したがって、値を返さない関数の戻り値の型はC言語、C++ともにvoid
にしましょう。
また、うっかり値を返す関数でreturn
文を実行させるのを忘れても気付けるよう、
コンパイラを警告を出す設定にしましょう。