73

Cで呼び出すときに、関数パラメーターの評価順序を想定できますか?次のプログラムによると、実行したときの順序は特にないようです。

#include <stdio.h>

int main()
{
   int a[] = {1, 2, 3};
   int * pa; 

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
   /* Result: a[0] = 3  a[1] = 2    a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(pa),*(++pa));
   /* Result: a[0] = 2  a[1] = 2     a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(++pa), *(pa));
   /* a[0] = 2  a[1] = 2 a[2] = 1 */

}
4

7 に答える 7

71

いいえ、関数パラメーターはCで定義された順序で評価されません。

C++プログラマーが知っておくべき一般的な未定義動作は何ですか?に対するMartinYorkの回答を参照してください。。

于 2008-12-17T22:31:20.083 に答える
22

C99 §6.5.2.2p10 から、関数の引数の評価の順序は指定されていません。

関数指定子、実引数、および実引数内の部分式の評価順序は規定されていませんが、実際の呼び出しの前にシーケンス ポイントがあります。

C89 にも同様の文言が存在します。

さらに、pa未定義の動作を引き起こすシーケンス ポイントを介在させずに複数回変更しています (コンマ演算子はシーケンス ポイントを導入しますが、関数の引数を区切るコンマは導入しません)。コンパイラで警告を表示すると、次のように警告されるはずです。

$ gcc -Wall -W -ansi -pedantic test.c -o test
test.c: In function ‘main’:
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:20: warning: control reaches end of non-void function
于 2008-12-17T22:46:57.493 に答える
17

いくつかの経験を追加するだけです。
次のコード:

int i=1;
printf("%d %d %d\n", i++, i++, i);

結果は

2 1 3- Linux.i686 で g++ 4.2.1 を使用
1 2 3- Linux.i686 で SunStudio C++ 5.9 を使用
2 1 3- SunOS.x86pc で g++ 4.2.1 を使用
1 2 3- SunOS.x86pc で SunStudio C++ 5.9 を使用- SunOS.sun4u
1 2 3で g++ 4.2.1 を使用
1 2 3- を使用SunOS.sun4u 上の SunStudio C++ 5.9

于 2011-06-15T12:36:28.180 に答える
11

C で呼び出すときに、関数パラメーターの評価順序を想定できますか?

いいえ、未指定の動作であると仮定することはできません。C99 標準の草案のセクション6.5パラグラフに3は次のように記載されています。

演算子とオペランドのグループ化は、構文によって示されます.74) 後で指定される場合を除き (関数呼び出し ()、&&、||、?:、およびカンマ演算子について)、部分式の評価の順序およびどちらの副作用が発生するかは、どちらも特定されていません。

それはまた、 except as specified later および 具体的には sites と述べているため、セクション関数呼び出しのパラグラフfunction-call ()の草案標準の後半で次のように述べていることがわかります。6.5.2.2 10

関数指定子、実引数、および実引数内の部分式の評価順序は指定されていませんが、実際の呼び出しの前にシーケンス ポイントがあります。

このプログラムは、シーケンス ポイント間で複数回変更しているため、未定義の動作も示します。ドラフト標準セクションの段落から:pa6.52

前のシーケンス ポイントと次のシーケンス ポイントの間で、オブジェクトの格納値は、式の評価によって最大 1 回変更されます。さらに、以前の値は、保存する値を決定するためにのみ読み取られます。

次のコード例は未定義として引用されています。

i = ++i + 1;
a[i++] = i; 

コンマ演算子はシーケンス ポイントを導入しますが、関数呼び出しで使用されるコンマはセパレータであり、comma operator. セクション6.5.17 Comma operatorの段落を見ると、次のように書かれ2ています。

コンマ演算子の左側のオペランドは void 式として評価されます。その評価の後にシーケンス ポイントがあります。

しかし、段落3は言う:

例 構文で示されているように、リスト内の項目を区切るためにコンマが使用されているコンテキスト (関数への引数や初期化子のリストなど) では、コンマ演算子 (この節で説明されている)を使用することはできません。

gccこれを知らずに、少なくとも使用して警告をオンにすると-Wall、次のようなメッセージが表示されます。

warning: operation on 'pa' may be undefined [-Wsequence-point]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                                            ^

デフォルトclangでは、次のようなメッセージで警告します。

warning: unsequenced modification and access to 'pa' [-Wunsequenced]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                            ~         ^

一般に、最も効果的な方法でツールを使用する方法を理解することが重要です。警告に使用できるフラグを知ることは重要です。その情報はここでgcc見つけることができます。便利で、長期的には多くのトラブルを回避できるいくつかのフラグは、 と の両方に共通です。理解するには-fsanitizeが非常に役立ちます。たとえば、実行時に未定義の動作の多くのインスタンスをキャッチします。gccclang-Wextra -Wconversion -pedanticclang -fsanitize=undefined

于 2013-08-15T03:04:24.883 に答える
6

他の人がすでに言ったように、関数の引数が評価される順序は指定されておらず、それらを評価する間にシーケンスポイントはありません。paその後、各引数を渡すときに変更するためpa、2つのシーケンスポイント間で変更して2回読み取ります。これは実際には未定義の動作です。GCCマニュアルで非常に良い説明を見つけました。これは役立つと思います。

CおよびC++標準は、C / C ++プログラムの式がシーケンスポイントの観点から評価される順序を定義します。これは、プログラムの一部の実行(シーケンスポイントの前に実行されるものと、後に実行されるもの)の間の半順序を表します。それ。これらは、完全な式(より大きな式の一部ではない式)の評価後、&&、|| 、?の最初のオペランドの評価後に発生します。:または、(コンマ)演算子、関数が呼び出される前(ただし、その引数と呼び出された関数を示す式の評価後)、およびその他の特定の場所。シーケンスポイントルールで表現されている場合を除き、式の部分式の評価順序は指定されていません。これらのルールはすべて、たとえば、次のように、全体の順序ではなく、部分的な順序のみを記述します。1つの式内で2つの関数が呼び出され、それらの間にシーケンスポイントがない場合、関数が呼び出される順序は指定されません。ただし、標準化委員会は、関数呼び出しが重複しないことを決定しました。

シーケンスポイント間でオブジェクトの値の変更が有効になる時期は指定されていません。動作がこれに依存するプログラムには、未定義の動作があります。CおよびC++標準では、「前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、保存される値を決定するためにのみ読み取られるものとします。」プログラムがこれらのルールに違反した場合、特定の実装での結果は完全に予測できません。

未定義動作のコードの例は、a = a ++;、a [n] = b [n ++]、およびa [i ++]=i;です。いくつかのより複雑なケースはこのオプションでは診断されず、時折誤検知の結果が生じる可能性がありますが、一般に、プログラムでこの種の問題を検出するのにかなり効果的であることがわかっています。

この標準は紛らわしい言い回しであるため、微妙な場合のシーケンスポイントルールの正確な意味についてはいくつかの議論があります。提案された正式な定義を含む、問題の議論へのリンクは、GCCリーディングページ(http://gcc.gnu.org/readings.html )にあります。

于 2008-12-28T08:53:58.240 に答える
1

式で変数を複数回変更することは、未定義の動作です。したがって、異なるコンパイラでは異なる結果が得られる可能性があります。したがって、変数を複数回変更することは避けてください。

于 2014-01-26T10:38:41.963 に答える