はい、これは宿題の質問ですが、私は自分の研究とトピックに関するかなりの量の深い考えを行ったので、これを理解することはできません。質問では、このコードは 短絡動作 を示さないことを示し、その理由を尋ねます。しかし、私にはそれが短絡動作を示すように見えるので、誰かがそれをしない理由を説明できますか?
Cで:
int sc_and(int a, int b) {
return a ? b : 0;
}
a
がfalseの場合、プログラムはb
をまったく評価しようとしませんが、間違っているに違いありません。なぜ必要ないのに、この場合、プログラムはb
にさえ触れるのですか?
これはトリックの質問です。 b
は_sc_and
_メソッドへの入力引数であるため、常に評価されます。つまり、sc_and(a(), b())
はa()
を呼び出し、b()
(順序は保証されません)を呼び出し、a(), b()
の結果で_sc_and
_を呼び出します。 _a?b:0
_に渡します。三項演算子自体とは関係がなく、絶対に短絡します。
[〜#〜] update [〜#〜]
私がこれを「トリック質問」と呼んだ理由に関して:それは「短絡」(少なくともOPによって再現された)を考慮する場所について明確に定義されたコンテキストがないためです。多くの人は、関数定義だけを与えられた場合、質問のコンテキストが関数のbodyについて尋ねていると仮定します ;多くの場合、関数自体を式と見なしません。これは質問の「トリック」です。一般的なプログラミングでは、特にルールに多くの例外があるCライクのような言語では特に注意してください。これはできません。例、質問がそのように尋ねられた場合:
次のコードを検討してください。 mainから呼び出された場合、sc_andは短絡動作を示します。
_int sc_and(int a, int b){
return a?b:0;
}
int a(){
cout<<"called a!"<<endl;
return 0;
}
int b(){
cout<<"called b!"<<endl;
return 1;
}
int main(char* argc, char** argv){
int x = sc_and(a(), b());
return 0;
}
_
_sc_and
_を自分自身のドメイン固有言語の演算子として考え、そして_sc_and
_の呼び出しは、通常の_&&
_のような短絡動作を示します。あなたは三項演算子に焦点を合わせるべきではないことは明らかであり、代わりにC/C++の関数呼び出しの仕組みに焦点を当てるべきであることが明らかであるため、私はそれをトリックの質問であるとは考えませんショートサーキットを行う_sc_and
_を書くためのフォローアップの質問にうまく入ります。これは関数ではなく_#define
_を使用することを伴います.
三項演算子自体がショートサーキット(または「条件付き評価」など)を行うことを呼び出すかどうかは、ショートサーキットの定義に依存し、そのことについてのさまざまなコメントを読むことができます。私の場合はそうですが、実際の質問や私がそれを「トリック」と呼んだ理由とはまったく関係ありません。
声明が
bool x = a && b++; // a and b are of int type
オペランドa
がfalse
に評価された場合(短絡動作)、b++
は評価されません。これは、b
に対する副作用が起こらないことを意味します。
次に、関数を見てください。
bool and_fun(int a, int b)
{
return a && b;
}
そしてこれを呼ぶ
bool x = and_fun(a, b++);
この場合、a
がtrue
であるかfalse
であるか、b++
は常に関数呼び出し中に評価され、b
に対する副作用は常に評価されます起こります.
同じことが当てはまります
int x = a ? b : 0; // Short circuit behavior
そして
int sc_and (int a, int b) // No short circuit behavior.
{
return a ? b : 0;
}
他の人がすでに指摘したように、2つの引数として関数に渡されるものに関係なく、渡されると評価されます。これは、10進操作の前の方法です。
一方、これ
#define sc_and(a, b) \
((a) ?(b) :0)
would "short-circuit"、これはmacroは関数呼び出しを意味せず、関数の引数の評価もなし(s)が実行されます。
@cmastersコメントに記載されているエラーを修正するために編集されました。
に
int sc_and(int a, int b) {
return a ? b : 0;
}
... return
ed式は短絡評価を示しますが、関数呼び出しは示しません。
電話してみてください
sc_and (0, 1 / 0);
関数呼び出しは1 / 0
を評価しますが、決して使用されることはないため、ゼロ除算エラーを引き起こします。
(案)ANSI C規格からの関連抜粋:
2.1.2.3プログラムの実行
...
抽象マシンでは、すべての式はセマンティクスの指定に従って評価されます。実際の実装では、その値が使用されておらず、必要な副作用(関数の呼び出しや揮発性オブジェクトへのアクセスに起因するものを含む)が発生していないと推定できる場合、式の一部を評価する必要はありません。
そして
3.3.2.2関数呼び出し
....
意味論
...
関数の呼び出しの準備では、引数が評価され、各パラメーターに対応する引数の値が割り当てられます。
私の推測では、各引数は式として評価されますが、引数リスト全体はnot式であるため、非SCEの動作は必須です。
C規格の深海の表面の水しぶきとして、次の2つの側面に関する適切な情報に基づいた見解に感謝します。
1 / 0
を評価すると、未定義の動作が発生しますか?追伸.
C++に移行し、sc_and
をinline
関数として定義しても、SCEをnot取得します。 @ alk のようにCマクロとして定義すると、間違いなくそうなります。
三項演算の短絡を明確に見るには、整数の代わりに関数ポインターを使用するようにコードを少し変更してみてください。
_int a() {
printf("I'm a() returning 0\n");
return 0;
}
int b() {
printf("And I'm b() returning 1 (not that it matters)\n");
return 1;
}
int sc_and(int (*a)(), int (*b)()) {
a() ? b() : 0;
}
int main() {
sc_and(a, b);
return 0;
}
_
そして、それをコンパイルします(最適化がほとんどない場合でも:_-O0
_!)。 b()
がfalseを返す場合、a()
が実行されないことがわかります。
_% gcc -O0 tershort.c
% ./a.out
I'm a() returning 0
%
_
生成されたアセンブリは次のようになります。
_ call *%rdx <-- call a()
testl %eax, %eax <-- test result
je .L8 <-- skip if 0 (false)
movq -16(%rbp), %rdx
movl $0, %eax
call *%rdx <- calls b() only if not skipped
.L8:
_
したがって、他の人が正しく指摘したように、質問のトリックは、ショートしないコール(値で呼び出す)のパラメータ評価ではなく、ショートする(条件付き評価を呼び出す)三項演算子の動作に焦点を当てることです。
Cの三項演算子は、式a(条件)のみを評価して式bおよびc(値が返される可能性がある場合)。
次のコード:
int ret = a ? b : c; // Here, b and c are expressions that return a value.
次のコードとほぼ同等です。
int ret;
if(a) {ret = b} else {ret = c}
式aは、&&や||などの他の演算子で形成できます。値を返す前に2つの式を評価する可能性があるため、短絡する可能性がありますが、それは短絡を行う三項演算子とは見なされませんが、通常のif文のように条件で使用される演算子と見なされます.
更新:
三項演算子が短絡演算子であることについては、いくつかの議論があります。引数は、すべてのオペランドを評価しない演算子は、以下のコメントの@aruisdanteに従って短絡することを示しています。この定義が与えられた場合、三項演算子は短絡することになり、この場合、これは私が同意する元の定義です。問題は、「短絡」という用語がもともとこの動作を許可する特定の種類の演算子に使用されていたことであり、それらは論理/ブール演算子であり、それらだけである理由は私が説明しようとするものです。
記事 短絡評価 に続いて、短絡評価は、第1オペランドが第2オペランドを無関係にすることを知っている方法で言語に実装されたブール演算子にのみ参照されます。 &&演算子が最初のオペランドfalseであり、||第1オペランドである演算子true、 C11仕様 は、6.5.13論理AND演算子および6.5.14論理OR演算子。
これは、短絡動作を識別するために、最初のオペランドが2番目のオペランドと無関係にならない場合、ブール演算子のようにすべてのオペランドを評価する必要がある演算子で識別することを期待することを意味します。これは、短絡が論理演算子から来るため、「論理短絡」セクションの下の MathWorks の短絡の別の定義に書かれているものと一致しています。
私は三項とも呼ばれるC三項演算子を説明しようとしてきたので、オペランドの2つのみを評価し、最初の1つを評価し、次に2つ目の値を評価します。最初の1つ。それは常にこれを行い、どのような状況でも3つすべてを評価することを想定していないため、どのような場合でも「短絡」はありません。
いつものように、もしあなたが何かが正しくないと思うなら、これに反対する引数を付けてコメントを書いてください。それは単にSOの経験を悪化させます。ただ投票するだけで賛成できないという、より良いコミュニティです。