web-dev-qa-db-ja.com

C演算子の優先順位と結合性を定義するのは誰ですか?

前書き

C/C++のすべての教科書には、次のような演算子の優先順位と結合性の表があります。

Operator Precedence And Associativity Table

http://en.cppreference.com/w/cpp/language/operator_precedence

StackOverflowに関する質問の1つは、次のような質問をしました。

次の関数はどのような順序で実行されますか。

f1() * f2() + f3();
f1() + f2() * f3();

前のグラフを参照して、関数には左から右への結合性があると自信を持って答えたので、前のステートメントでは、どちらの場合も次のように評価されます。

f1()-> f2()-> f3()

関数が評価された後、次のように評価を終了します。

(a1 * a2)+ a3
a1 +(a2 * a3)

驚いたことに、多くの人が私が間違っていると言っていました。それらが間違っていることを証明することを決心し、私はANSIC11規格に目を向けることにしました。演算子の優先順位と結合性についてほとんど言及されていないことにもう一度驚いた。

質問

  1. 関数が常に左から右に評価されるという私の信念が間違っている場合、関数の優先順位と結合性を参照する表は実際にはどういう意味ですか?
  2. ANSIでない場合、演算子の優先順位と結合性を定義するのは誰ですか?定義を行うのがANSIである場合、演算子の優先順位と結合性についてほとんど言及されていないのはなぜですか?演算子の優先順位と結合性はANSIC標準から推測されますか、それとも数学で定義されていますか?
26
Fiddling Bits

演算子の優先順位は、適切な標準で定義されています。 CとC++の標準は、CとC++が正確に何であるかを定義する1つの真の定義です。よく見ると、詳細はそこにあります。実際、詳細は言語の文法にあります。たとえば、C++の+-の文法生成規則を見てください(まとめてadditive-expressions):

additive-expression:
  multiplicative-expression
  additive-expression + multiplicative-expression
  additive-expression - multiplicative-expression

ご覧のとおり、multiplicative-expressionadditive-expressionのサブルールです。これは、x + y * zのようなものがある場合、y * z式はx + y * zの部分式であることを意味します。これは、これら2つの演算子間のprecedenceを定義します。

また、additive-expressionの左側のオペランドが別のadditive-expressionに展開されていることもわかります。つまり、x + y + zを使用すると、 x + yはその部分式です。これは、結合性を定義します。

結合性は、同じ演算子の隣接する使用をどのようにグループ化するかを決定します。たとえば、+は左から右への結合です。つまり、x + y + zは次のようにグループ化されます:(x + y) + z

これを評価の順序と間違えないでくださいx + yが計算される前にzの値を計算できなかった理由はまったくありません。重要なのは、計算されるのはx + yであり、y + zではないということです。

関数呼び出し演算子の場合、左から右への結合性は、f()()(たとえば、fが関数ポインターを返した場合に発生する可能性があります)が次のようにグループ化されることを意味します。(f())()(もちろん、他の方向は意味がありません)。

今、あなたが見ていた例を考えてみましょう:

f1() + f2() * f3()

*演算子は+演算子よりも優先順位が高いため、式は次のようにグループ化されます。

f1() + (f2() * f3())

同じ演算子が互いに隣接していないため、ここで結合性を考慮する必要はありません。

ただし、関数呼び出し式の評価は完全に順序付けられていません。最初にf3を呼び出し、次にf1を呼び出し、次にf2を呼び出すことができなかった理由はありません。この場合の唯一の要件は、演算子が評価される前に、演算子のオペランドが評価されることです。つまり、f2が評価される前にf3*が呼び出され、*が評価され、f1が評価される前に+が呼び出される必要があります。

ただし、一部の演算子は、オペランドの評価に順序付けを課します。たとえば、x || yでは、xは常にyの前に評価されます。これにより、短絡が可能になります。yがすでにxであることがわかっている場合は、trueを評価する必要はありません。

評価の順序は、以前はシーケンスポイントを使用してCおよびC++で定義されていましたが、どちらも用語を変更して、シーケンスの観点から物事を定義しています。 before関係。詳細については、 未定義の動作とシーケンスポイント を参照してください。

43

C標準での演算子の優先順位は、構文で示されます。

(C99、6.5p3)「演算子とオペランドのグループ化は構文で示されます。74)」

74)「構文は式の評価における演算子の優先順位を指定します」

C99の理論的根拠も

「優先規則は、各演算子の構文規則にエンコードされています。」

そして

「結合性の規則は、同様に構文規則にエンコードされます。」

また、結合性は評価順序とは関係がないことに注意してください。に:

f1()* f2()+ f3()

関数呼び出しは任意の順序で評価されます。 Cの構文規則では、f1() * f2() + f3()(f1() * f2()) + f3()を意味しますが、式のオペランドの評価順序は指定されていません。

10
ouah

優先順位と結合性について考える1つの方法は、言語が複数の演算子ではなく、割り当てと1つの演算子を含むステートメントのみを許可することを想像することです。したがって、次のようなステートメント:

a = f1() * f2() + f3();

3つの関数呼び出し、乗算、加算の5つの演算子があるため、許可されません。この単純化された言語では、すべてを一時的なものに割り当ててから、それらを組み合わせる必要があります。

temp1 = f1();
temp2 = f2();
temp3 = temp1 * temp2;
temp4 = f3();
a = temp3 + temp4;

乗算は加算よりも優先されるため、結合性と優先順位は、最後の2つのステートメントをこの順序で実行する必要があることを指定します。ただし、最初の3つのステートメントの相対的な順序は指定されていません。それは同じように有効です:

temp4 = f3();
temp2 = f2();
temp1 = f1();
temp3 = temp1 * temp2;
a = temp3 + temp4;

sftrabbitは、関数呼び出し演算子の結合性が関連する例を示しました。

a = f()();

上記のように単純化すると、次のようになります。

temp = f();
a = temp();
8
Barmar

優先順位と結合性は標準で定義されており、構文ツリーの構築方法を決定します。優先順位は演算子タイプによって機能し(_1+2*3_は1+(2*3)であり、_(1+2)*3_ではありません)、結合性は演算子の位置によって機能します(_1+2+3_は_(1+2)+3_であり、1+(2+3))。

評価の順序は異なります-構文ツリーの構築方法を定義しません-構文ツリー内の演算子のノードをevaluateする方法を定義します。評価の順序は定義されていないように定義されています。コンパイラーは適切と思われる順序を自由に選択できるため、評価の順序を信頼することはできません。これは、コンパイラがコードの最適化を試みることができるようにするために行われます。プログラマーは、評価の順序に影響されないコードを記述し、順序に関係なく同じ結果を生成するという考え方です。

4
Idan Arye

左から右への結合性は、f() - g() - h()が_(f() - g()) - h()_を意味し、それ以上のものではないことを意味します。 fが_1_を返すとします。 gが_2_を返すとします。 hが_3_を返すとします。左から右への結合性は、結果が_(1 - 2) - 3_または_-4_であることを意味します。コンパイラーは最初にgおよびhを呼び出すことができますが、これは何の関係もありません。結合性がありますが、1 - (2 - 3)の結果を与えることは許可されていません。これは、まったく異なるものになります。

3
user743382