問題の歴史
return演算子は、関数の作業を明示的に終了し、呼び出しコードに結果を渡すためにCで登場しました。初期のプログラミング言語では値を返す機能が常に存在したわけではなく、returnメカニズムによって計算結果を明示的に指定できるようになりました。これによりプログラムの表現力と安全性が向上しました。
問題
主な課題は、関数を正しく終了させ、必要に応じて特定の型に対応する値を返すことです。間違った型の値を返したり、存在しないポインタや局所変数を返したり、呼び出し側が返された値を無視したりすることでエラーが多く発生します。
解決策
コード例:
#include <stdio.h> struct Point { int x, y; }; struct Point make_point(int x, int y) { // 構造体を返す(コピー) struct Point p = {x, y}; return p; } int* dangerous() { int num = 42; return # // 危険:局所変数のアドレスを返す! } void do_nothing() { return; // void型の関数に対しては正しい } int main() { struct Point p = make_point(3, 4); printf("%d %d\n", p.x, p.y); int* ptr = dangerous(); // UB: ptrは解放された領域を指す }
主な特徴:
値を持たない関数(void)でreturnを使用できますか?
答え:はい、「return;」を書くことはvoid関数で可能ですが、式(return x;)をvoid関数で指定することはできません。
関数から配列を返すとどうなりますか?
答え:Cでは配列を直接返すことはできません。ポインタ(例:静的配列へのポインタ)を返すことはできますが、より一般的にはポインタとサイズを返すか、動的に割り当てた配列を使用すべきです。
int* make_arr() { static int arr[5] = {1,2,3,4,5}; return arr; // 静的配列は関数を出た後も存在し続ける }
局所変数へのポインタを返すことが危険な理由は何ですか?
答え:関数を出ると局所変数に対するメモリは解放されます(スタック領域)。返されたポインタを使うことは未定義の動作を引き起こします。
ネガティブケース
関数が局所変数へのポインタを返し、呼び出し側が「ゴミ」を受け取り、予測できない動作やまれなバグが発生します。
利点:
欠点:
ポジティブケース
返される構造体を使用する(値でコピー)または静的/動的メモリへのポインタを返す:
利点:
欠点: