C言語/ Noreturn
はじめに
[編集]_NoreturnはC11で導入された関数指定子で、関数が呼び出し元に戻らないことをコンパイラに通知するためのものです。C23においても引き続き使用可能ですが、新しい属性構文である[[noreturn]]の導入により、徐々に置き換えられる傾向にあります。本章では、_Noreturnの意味、使用法、[[noreturn]]属性との関係、実装上の留意点について解説します。
_Noreturnの意味と目的
[編集]_Noreturn指定子は、関数が呼び出し元に制御を戻さないことをコンパイラに明示的に伝えるものです。これにより、コンパイラは以下のような最適化や警告を行うことができます:
- 呼び出し後のコードの不到達性(unreachability)を正確に検出
- 戻り値チェックの不要な警告を抑制
- 関数呼び出し周辺のコードの最適化
典型的な例としては、exit、abort、longjmpなどを呼び出す関数や、無限ループを持つ関数が挙げられます。
構文と使用法
[編集]_Noreturnの基本的な構文は次のとおりです:
_Noreturn void function_name(parameters);
実際の使用例:
#include <stdlib.h> _Noreturn void terminate_program(int exit_code) { /* クリーンアップコード */ exit(exit_code); /* この関数は戻らない */ } int main(void) { terminate_program(0); /* この呼び出し後のコードは実行されない */ return 0; /* 不到達コード - コンパイラは警告を出す可能性がある */ }
_Noreturnと<stdnoreturn.h>
[編集]C11では<stdnoreturn.h>ヘッダも導入され、より読みやすいnoreturnマクロを提供しています:
#include <stdnoreturn.h> #include <stdlib.h> noreturn void terminate_program(int exit_code) { exit(exit_code); }
このマクロは単に_Noreturnに展開されますが、通常のコード中でより読みやすい名前を使用できるようになります。C23でも<stdnoreturn.h>ヘッダは引き続きサポートされています。
_Noreturnの制約と注意点
[編集]_Noreturn指定された関数が実際に呼び出し元に戻った場合、動作は未定義となります。これは重要な制約であり、_Noreturnを使用する際には細心の注意が必要です:
_Noreturn void incorrect_usage(int value) { if (value > 0) { printf("Positive value\n"); return; /* 違反: _Noreturn関数が戻っている */ } exit(1); }
上記のコードは、valueが正の場合に関数が戻るため、_Noreturnの制約に違反しています。
C23での_Noreturnと[[noreturn]]属性
[編集]C23では、_Noreturn指定子に加えて新しい[[noreturn]]属性も導入されました。両者は機能的に同等ですが、長期的には属性構文の使用が推奨されています:
/* C11/C23での従来の書き方 */ _Noreturn void old_style_exit(int code) { exit(code); } /* C23での新しい書き方 */ [[noreturn]] void new_style_exit(int code) { exit(code); }
C23では両方の構文がサポートされているため、既存のコードの互換性は維持されます。以下は両者の対応関係を示す表です:
| 機能 | C11 | C23 |
|---|---|---|
| 関数が戻らないことの指定 | _Noreturn
|
_Noreturnまたは[[noreturn]]
|
<stdnoreturn.h>サポート
|
あり | ← |
| 属性構文サポート | なし | あり([[noreturn]])
|
| 将来的な推奨 |
|
[[noreturn]]
|
実装例と使用パターン
[編集]エラーハンドリング関数
[編集]#include <stdio.h> #include <stdlib.h> _Noreturn void fatal_error(const char* message) { fprintf(stderr, "致命的エラー: %s\n", message); exit(EXIT_FAILURE); } int main(void) { FILE* file = fopen("nonexistent.txt", "r"); if (!file) { fatal_error("ファイルを開けませんでした"); } /* この行は実行されない(fatal_errorが呼ばれた場合) */ fclose(file); return 0; }
アサーション関数
[編集]#include <stdio.h> #include <stdlib.h> _Noreturn void assert_failed(const char* expression, const char* file, int line) { fprintf(stderr, "アサーション失敗: %s at %s:%d\n", expression, file, line); abort(); } #define ASSERT(expr) \ ((expr) ? (void)0 : assert_failed(#expr, __FILE__, __LINE__)) int divide(int a, int b) { ASSERT(b != 0); return a / b; } int main(void) { int result = divide(10, 0); /* アサーション失敗 */ printf("%d\n", result); /* 実行されない */ return 0; }
無限ループの明確化
[編集]#include <stdnoreturn.h> noreturn void event_loop(void) { while (1) { /* イベント処理 */ process_events(); /* この関数は決して戻らない */ } } int main(void) { event_loop(); return 0; /* 不到達コード */ }
コンパイラの最適化と警告
[編集]_Noreturn(または[[noreturn]])を使用すると、コンパイラは以下のような最適化や警告を行うことができます:
int function(void) { int x = 5; if (condition()) { fatal_error("エラー条件が発生しました"); } /* コンパイラは fatal_error が戻らないことを知っているため、 conditionがtrueの場合、この行に到達しないことを認識できる */ return x + 10; }
上記のコードでは、fatal_errorが_Noreturnで宣言されていれば、コンパイラはcondition()が真の場合にその後のコードが実行されないことを認識できます。
実装上の注意点
[編集]戻り値の型
[編集]_Noreturnは通常、void型の関数に使用されますが、他の戻り値型を持つ関数にも適用できます:
_Noreturn int never_returns(void) { exit(1); /* 戻り値は実際には使用されない */ }
ただし、関数は戻らないため戻り値は無視されます。可読性のためにvoid型を使用するのが一般的です。
関数ポインタ型での使用
[編集]_Noreturnは関数ポインタ型の宣言にも使用できます:
typedef _Noreturn void (*error_handler_t)(const char*); error_handler_t set_error_handler(error_handler_t new_handler) { error_handler_t old_handler = current_handler; current_handler = new_handler; return old_handler; }
インライン関数での使用
[編集]_Noreturnはインライン関数でも使用できます:
#include <stdlib.h> static inline _Noreturn void quick_exit(int status) { exit(status); }
C++との互換性
[編集]C++には[[noreturn]]属性が存在しますが、_Noreturn指定子はC特有のものです。C/C++の混合コードを書く場合は、C23の[[noreturn]]属性を使用するか、条件付きコンパイルを使用して対応する必要があります:
#ifdef __cplusplus [[noreturn]] void fatal_error(const char* message); #else _Noreturn void fatal_error(const char* message); #endif
C23では、[[noreturn]]属性を使用することでC++との互換性も確保できるようになりました。
標準ライブラリとの関係
[編集]C標準ライブラリには_Noreturnが適用される関数がいくつか含まれています:
| 関数 | ヘッダ | 説明 |
|---|---|---|
exit
|
<stdlib.h>
|
正常または異常終了 |
quick_exit
|
<stdlib.h>
|
高速な異常終了 |
_Exit
|
<stdlib.h>
|
最小限の後処理で終了 |
abort
|
<stdlib.h>
|
異常終了 |
thrd_exit
|
<threads.h>
|
現在のスレッドを終了 |
これらの関数はいずれも呼び出し元に戻らないため、内部的に_Noreturnが使用されています。
まとめ
[編集]_Noreturn関数指定子はC11で導入され、C23でも完全にサポートされている機能です。関数が呼び出し元に戻らないことをコンパイラに通知することで、コードの正確性向上と最適化の促進に貢献します。C23では新たに[[noreturn]]属性も導入され、同等の機能を提供しつつより現代的な構文を実現しています。
将来的には[[noreturn]]属性の使用が推奨されますが、既存コードとの互換性のため_Noreturnは今後も長くサポートされるでしょう。どちらを使用する場合も、その指定子や属性が持つ意味合いと制約を正確に理解し、適切に使用することが重要です。