web-dev-qa-db-ja.com

Cで呼び出す関数の前のパラメーター評価順序

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 */

}
67
corto

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

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

63
Grant Wagner

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
19
Robert Gamble

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

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を使用する
1 2 3-SunOS.Sun4uでg ++ 4.2.1を使用
1 2 3-SunOS.Sun4uでSunStudio C++ 5.9を使用する

14
Pitje Puck

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

いいえ、 指定されていない動作 、section _6.5_段落_3_の C99標準案 である場合は想定できません。

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

また、後で指定されている場合を除き、具体的にはfunction-call ()を除いているため、標準ドラフトのセクション_6.5.2.2_Function callsパラグラフ_10_言う:

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

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

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

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

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

comma operator はシーケンスポイントを導入しますが、関数呼び出しで使用されるコンマは_comma operator_ではなくセパレーターであることに注意してください。セクション_6.5.17_カンマ演算子段落_2_を見ると、

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

しかし、段落_3_は言う:

例構文で示されているように、コンマ演算子(この節で​​説明)は、コンマを使用してリスト内の項目を分離するコンテキスト(関数の引数やイニシャライザ)。

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

_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の場合は、その情報を見つけることができます here 。便利で、長い目で見れば多くのトラブルを軽減し、gccclangの両方に共通するフラグは_-Wextra -Wconversion -pedantic_です。 clangの場合 理解-fsanitize は非常に役立ちます。たとえば、_-fsanitize=undefined_は、実行時に未定義の動作の多くのインスタンスをキャッチします。

9
Shafik Yaghmour

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

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

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

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

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

式で変数を複数回変更することは未定義の動作です。そのため、コンパイラーによって結果が異なる場合があります。したがって、変数を複数回変更しないでください。

1
Pankaj Mahato