web-dev-qa-db-ja.com

このバージョンのCの論理ANDが短絡動作を示さないのはなぜですか?

はい、これは宿題の質問ですが、私は自分の研究とトピックに関するかなりの量の深い考えを行ったので、これを理解することはできません。質問では、このコードは 短絡動作 を示さないことを示し、その理由を尋ねます。しかし、私にはそれが短絡動作を示すように見えるので、誰かがそれをしない理由を説明できますか?

Cで:

int sc_and(int a, int b) {
    return a ? b : 0;
}

aがfalseの場合、プログラムはbをまったく評価しようとしませんが、間違っているに違いありません。なぜ必要ないのに、この場合、プログラムはbにさえ触れるのですか?

79
Bobazonski

これはトリックの質問です。 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_を使用することを伴います.

三項演算子自体がショートサーキット(または「条件付き評価」など)を行うことを呼び出すかどうかは、ショートサーキットの定義に依存し、そのことについてのさまざまなコメントを読むことができます。私の場合はそうですが、実際の質問や私がそれを「トリック」と呼んだ理由とはまったく関係ありません。

117
aruisdante

声明が

bool x = a && b++;  // a and b are of int type

オペランドafalseに評価された場合(短絡動作)、b++は評価されません。これは、bに対する副作用が起こらないことを意味します。

次に、関数を見てください。

bool and_fun(int a, int b)
{
     return a && b; 
}

そしてこれを呼ぶ

bool x = and_fun(a, b++);

この場合、atrueであるか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;
} 
42
haccks

他の人がすでに指摘したように、2つの引数として関数に渡されるものに関係なく、渡されると評価されます。これは、10進操作の前の方法です。

一方、これ

#define sc_and(a, b) \
  ((a) ?(b) :0)

would "short-circuit"、これはmacroは関数呼び出しを意味せず、関数の引数の評価もなし(s)が実行されます。

19
alk

@cmastersコメントに記載されているエラーを修正するために編集されました。


int sc_and(int a, int b) {
    return a ? b : 0;
}

... returned式は短絡評価を示しますが、関数呼び出しは示しません。

電話してみてください

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_andinline関数として定義しても、SCEをnot取得します。 @ alk のようにCマクロとして定義すると、間違いなくそうなります。

5
Thumbnail

三項演算の短絡を明確に見るには、整数の代わりに関数ポインターを使用するようにコードを少し変更してみてください。

_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:
_

したがって、他の人が正しく指摘したように、質問のトリックは、ショートしないコール(値で呼び出す)のパラメータ評価ではなく、ショートする(条件付き評価を呼び出す)三項演算子の動作に焦点を当てることです。

4
flaviodesousa

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オペランドである演算子trueC11仕様 は、6.5.13論理AND演算子および6.5.14論理OR演算子。

これは、短絡動作を識別するために、最初のオペランドが2番目のオペランドと無関係にならない場合、ブール演算子のようにすべてのオペランドを評価する必要がある演算子で識別することを期待することを意味します。これは、短絡が論理演算子から来るため、「論理短絡」セクションの下の MathWorks の短絡の別の定義に書かれているものと一致しています。

私は三項とも呼ばれるC三項演算子を説明しようとしてきたので、オペランドの2つのみを評価し、最初の1つを評価し、次に2つ目の値を評価します。最初の1つ。それは常にこれを行い、どのような状況でも3つすべてを評価することを想定していないため、どのような場合でも「短絡」はありません。

いつものように、もしあなたが何かが正しくないと思うなら、これに反対する引数を付けてコメントを書いてください。それは単にSOの経験を悪化させます。ただ投票するだけで賛成できないという、より良いコミュニティです。

0
Adrián Pérez