ノーリターンのポイントは何ですか?


189

[dcl.attr.noreturn]は次の例を提供します:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

しかし[[noreturn]]、関数の戻り値の型が既にあるので、私は何の意味があるのか​​わかりませんvoid

では、noreturn属性のポイントは何ですか?どのように使用することになっていますか?


1
このような注意が必要なこの種の機能(プログラムの実行中に1回発生する可能性が最も高い)の何がそれほど重要ですか?これは簡単に検出できる状況ではありませんか?
user666412

1
@MrLister OPは、「戻り値」と「戻り値」の概念を融合したものです。それらがほとんど常にタンデムで使用される方法を考えると、混乱は正当化されると思います。
Slipp D. Thompson 2016

回答:


209

noreturn属性は、呼び出し元に戻らない関数に使用されることになっています。これはvoid関数(呼び出し元に戻る-値を返さない)を意味するのではなく、制御フローが関数の終了後に呼び出し元の関数に戻らない関数(たとえば、アプリケーションを終了する関数、永久にループするか、例のように例外をスローします)。

コンパイラはこれを使用して、いくつかの最適化を行い、より適切な警告を生成できます。たとえばf、にnoreturn属性がある場合、コンパイラーは、g()書き込み時にデッドコードであることを警告する可能性がありますf(); g();。同様に、コンパイラーは、の呼び出し後にreturnステートメントが欠落していることについて警告しないことを知っていますf()


5
execveそのような関数返すべきではありませんが、できますか?noreturn属性が必要ですか?
カリッシュ2014年

22
いいえ、すべきではありません。制御フローが呼び出し元に戻る可能性がある場合は、noreturn属性を持っていてはなりません。noreturnなど、アボートたとえば、あなたが終了し(呼び出すため)、()、アサート(0) -あなたの関数が呼び出し元に戻ることができ、制御フローの前にプログラムを終了何かをすることが保証されている場合にのみ使用することができる
RavuAlHemio

6
@ SlippD.Thompson noreturn関数の呼び出しがtryブロックでラップされている場合、catchブロックからのコードはすべて再度到達可能としてカウントされます。
sepp2k 2016年

2
@ sepp2kかっこいい。だから、返すことは不可能ではなく、異常です。それは便利です。乾杯。
Slipp D. Thompson 2016

7
@ SlippD.Thompsonいいえ、戻ることは不可能です。例外をスローしても戻りません。したがって、すべてのパスがスローした場合はnoreturnです。その例外の処理は、戻ったのと同じではありません。try呼び出し後の内のコードはまだ到達不可能であり、到達できない場合void、戻り値の割り当てや使用は行われません。
Jon Hanna

62

noreturn関数が値を返さないことをコンパイラに伝えません。これは、制御フローが呼び出し元に戻らないことをコンパイラーに通知します。これにより、コンパイラーはさまざまな最適化を行うことができます-呼び出しの前後の揮発性の状態を保存および復元する必要はありません。そうでなければ、呼び出しに続くコードなどをデッドコードで排除できます。


29

関数が完了しないことを意味します。への呼び出し後、制御フローがステートメントにヒットすることはありませんf()

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

この情報は、コンパイラー/オプティマイザーによってさまざまな方法で使用できます。コンパイラーは、上記のコードに到達できないことを示す警告を追加できg()ます。また、継続をサポートするなど、さまざまな方法で実際のコードを変更できます。



4
@TemplateRex:でコンパイルする-Wno-returnと警告が表示されます。おそらくあなたが期待していたものではないかもしれませんが、おそらくコンパイラが何であるかを知ってい[[noreturn]]て、それを利用できることを伝えるには十分でしょう。(私は少し驚いています-Wunreachable-code...でキックしませんでした)
デビッド・ロドリゲス- dribeas

3
@TemplateRex:申し訳ありません-Wmissing-noreturnが、警告は、フロー解析によりにstd::cout到達できないと判断されたことを意味します。私は、アセンブリ生成を見て、手で新しい十分なGCCを持っていませんが、呼び出しがする場合、私は驚かないだろうoperator<<落とした
デビッド・ロドリゲス- dribeasを

1
これがアセンブリダンプです(-S -o-フラグはコリルにあります)。実際に「到達不能」コードをドロップします。興味深いことに、ヒントなしで到達不可能なコードを削除するに-O1は、もう十分[[noreturn]]です。
TemplateRex

2
@TemplateRex:すべてのコードは同じ変換単位内にあり、表示されるため、コンパイラー[[noreturn]]はコードからを推測できます。この変換単位が、どこか別の場所で定義された関数の宣言しか持っていない場合、コンパイラーは関数が返らないこと知らないため、そのコードをドロップできません。これは、属性がコンパイラを支援する場所です。
デビッドロドリゲス-ドリベス2013

17

以前の答えはノーリターンとは何かを正しく説明しましたが、なぜそれが存在しないのではありませんでした。「最適化」コメントが主な目的だとは思いません。返らない関数はまれであり、通常は最適化する必要はありません。むしろ、ノーリターンの主な存在理由は、偽陽性の警告を回避することだと思います。たとえば、次のコードを考えてみます。

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

abort()が「noreturn」とマークされていなかった場合、コンパイラーは、fが期待どおりに整数を返さないパスを持つこのコードについて警告した可能性があります。しかし、abort()はリターンなしとマークされているため、コードが正しいことがわかります。


リストされている他のすべての例ではvoid関数が使用されています。[[no return]]ディレクティブと非void戻り型の両方がある場合、どのように機能しますか?[[no return]]ディレクティブは、コンパイラが警告を返さない可能性について警告し、警告を無視する準備ができたときにのみ機能しますか?たとえば、コンパイラーは次のように実行しますか:「わかりました、これは非void関数です」*コンパイルを継続します* "ああ、このコードは返されない可能性があります!ユーザーに警告する必要がありますか?*"気にしないでください。キャリーオン」
Raleigh L.

2
私の例のnoreturn関数はf()ではなく、abort()です。非void関数noreturnをマークしても意味がありません。時々値を返し、時々戻る関数(良い例はexecve())は、noreturnとマークすることはできません。
Nadav Har'El

1
暗黙-フォールスルー別のそのような例である:stackoverflow.com/questions/45129741/...
チロSantilli郝海东冠状病六四事件法轮功

11

タイプは理論的に言えば、void他の言語で呼ばれているものですunittop。これに相当するものはTrueです。任意の値を正当にキャストできますvoid(すべてのタイプはのサブタイプですvoid)。それを「ユニバース」セットと考えてください。世界中のすべての値に共通する操作がないため、typeの値に対して有効な操作はありませんvoid。言い換えれば、何かがユニバ​​ースセットに属していることを伝えると、情報はまったく提供されません-すでに知っています。したがって、以下は健全です。

(void)5;
(void)foo(17); // whatever foo(17) does

しかし、以下の割り当てはそうではありません:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]]一方、時々呼ばれemptyNothingBottomまたはBotとの論理等価である。値はまったくなく、このタイプの式は、任意のタイプにキャストできます(つまり、そのサブタイプです)。これは空のセットです。誰かが「式foo()の値は空のセットに属している」と言った場合、それは非常に有益です-この式は通常の実行を完了しないことを示しています。中止、スロー、またはハングします。これはの正反対ですvoid

したがって、以下は意味がありません(noreturnファーストクラスのC ++型ではないため、疑似C ++)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

ただし、以下の割り当てはthrow、コンパイラによって返されないことが理解されているため、完全に正当です。

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

完璧な世界では、上記noreturnの関数の戻り値として使用できますraise()

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

悲しいことに、C ++はそれを許可しません。おそらく実用上の理由からでしょう。代わりに、[[ noreturn ]]コンパイラーの最適化と警告のガイドに役立つ属性を使用することができます。


5
何かにキャストしvoidたりvoid評価しtrueたりfalseすることはできません。
2015年

5
私が言うときtrue、私は「true型の値」を意味するのではboolなく、論理的意味を参照してください。カリーハワード通信
Elazar

8
特定の言語の型システムに当てはまらない抽象型理論は、その言語の型システムを論じるときには無関係です。質問:-)は、型理論ではなくC ++に関するものです。
2015年

8
(void)true;答えが示唆するように、完全に有効です。void(true)構文的に完全に異なるものです。これは、引数としてvoidコンストラクタを呼び出して、タイプの新しいオブジェクトを作成する試みtrueです。これは、voidファーストクラスではないため、他の理由の中でも失敗します。
Elazar 2015年

2
そうです。Cスタイルのキャストではなく、type(value)のキャストに慣れています。
2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.