setjmpの結果を代入してはいけない
WasmLinuxで使っちゃってるし実際gccやclangでは正常に動作するんだけど一応...
setjmpを書いて良い場所
Cの規格やPOSIXでは setjmp
を書ける場所が厳密に決まっている:
An application shall ensure that an invocation of setjmp() appears in one of the following contexts only:
- The entire controlling expression of a selection or iteration statement
- One operand of a relational or equality operator with the other operand an integral constant expression, with the resulting expression being the entire controlling expression of a selection or iteration statement
- The operand of a unary '!' operator with the resulting expression being the entire controlling expression of a selection or iteration
- The entire expression of an expression statement (possibly cast to void)
要は分岐にしか書けない。例えば、
if(i = setjmp(jb)){ ... }
のように i
に代入するのは違反になる。
例えば https://www.jpcert.or.jp/sc-rules/c-msc22-c.html に挙がっているように、
if(i != setjmp(jb)){ ... }
のように式の中で比較するのは構わない。
longjmp
は値を取るんだよ
じゃぁ何で 具体的な値で比較することは違反ではないので、例えば longjmp
後の処理を switch
による分岐で指定するみたいなユースケースがある。
switch(setjmp(jb)){ case 1: ...; }
代入が動作しないケースは存在するのか?
自分の知る限り、実際には setjmp
の結果の代入ができないアーキテクチャは無い。これは、常識的なアーキテクチャでは基本的に setjmp
は複数回returnする通常のC ABIの関数として実装されているためだと思う。つまり、 setjmp
の呼出し側からは通常の関数に見えるため、代入を禁止するモチベーションが無い。
いわゆるSanitizer類はこのような利用をtrapできるかもしれない。
また、未定義挙動って事はコンパイラはそれを利用して最適化しても良いということでもあるので、正常動作は誰も保証してくれないということになる。
returns_twice
属性
gccやclangは returns_twice
属性を setjmp
や vfork
のような2度(以上)returnする関数に設定することを推奨している。gccは警告の制御くらいにしか言及していないが、ClangのバックエンドであるLLVMはtail-call最適化も無効にするとしている。
The
returns_twice
attribute tells the compiler that a function may return more than one time. The compiler ensures that all registers are dead before calling such a function and emits a warning about the variables that may be clobbered after the second return from the function. Examples of such functions aresetjmp
andvfork
. The longjmp-like counterpart of such function, if any, might need to be marked with thenoreturn
attribute.
(ClangではなくLLVM言語のマニュアル -- ただgccの returns_twice
属性は結局LLVMのこのattributeにlowerされる)
This attribute indicates that this function can return twice. The C
setjmp
is an example of such a function. The compiler disables some optimizations (like tail calls) in the caller of these functions.
呼出し側をtail-call最適化してしまうと、こんどは setjmp
呼出し側の関数を returns_twice
として扱わなければならなくなる。そのように感染させていってしまうのはよくない; C言語の setjmp
は、実際には setjmp
した関数自体から2度(以上)returnすることは無い -- そもそも setjmp
した関数から抜けたら longjmp
してはいけない -- ため、 returns_twice
を呼出し元にまで感染させていく必要性は本来存在しない。