ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris Studio 12.3: C ユーザーガイド Oracle Solaris Studio 12.3 Information Library (日本語) |
K&R C は、数学的に交換可能で結合可能な隣り合う演算子を含む式を再配置する権利を、括弧がある場合でもコンパイラに与えます。しかし、ISO C は、この権利をコンパイラに与えませんでした。
この節では、前述の 2 つの C の定義間の違いを説明します。また、次のコードにおける式文を考えることによって、式の副作用、グループ化、および評価の間の区別を明らかにします。
int i, *p, f(void), g(void); /*...*/ i = *++p + f() + g();
式の副作用とは、メモリーへの変更と、volatile 修飾オブジェクトへのアクセスのことです。サンプル式の副作用は、i と p の更新と、関数 f() と g() 内に含まれる任意の副作用です。
式のグループ化とは、値をほかの値や演算子と結合させる方法です。サンプル式のグループ化は、主に加算を実行する順番です。
式の評価には、その結果の値を生成するために必要なすべてが含まれます。式を評価するためには、指定したすべての副作用が以前のシーケンスポイントから次のシーケンスポイントまでの間で発生しなければならず、指定した演算が特定のグループ化で実行されなければいけません。サンプル式の場合、i と p の更新は、直前の文からこの式文の ; までの間で発生する必要があります。関数への呼び出しは、直前の文から戻り値が使用されるまでの間であれば、どちらが先に発生してもかまいません。特に、メモリーを更新する演算子には、演算の値が使用される前に新しい値を代入しなければならないという制約はありません。
サンプル式では加算が数学的に交換可能で結合可能であるため、K&R C の再配置の権利がサンプル式に適用されます。通常の括弧と実際の式のグループ化を区別するために、左右の中括弧でグループ化を示します。この式の場合、次の 3 つのグループ化が考えられます。
i = { {*++p + f()} + g() }; i = { *++p + {f() + g()} }; i = { {*++p + g()} + f() };
前述のすべてのグループ化は、K&R C の規則であれば有効です。さらに、たとえば、次のように式を書き換えた場合でも、前述のすべてのグループ化は有効です。
i = *++p + (f() + g()); i = (g() + *++p) + f();
オーバーフローによって例外が発生するか、あるいは、オーバーフローで加算と減算が逆にならないアーキテクチャー上でこの式が評価される場合、加算の 1 つがオーバーフローしたとき、前述の 3 つのグループ化の動作は異なります。
このようなアーキテクチャー上では、K&R C では、式を分割することによって強制的にグループ化するしか方法がありません。次の書き換え案はそれぞれ、前述の 3 つのグループ化を強制的に行います。
i = *++p; i += f(); i += g() i = f(); i += g(); i += *++p; i = *++p; i += g(); i += f();
ISO C では、数学的に交換可能で結合可能であるが、対象となるアーキテクチャー上では実際にそうではない演算を再配置することは許可されていません。したがって、ISO C 文法の優先度と結合規則によって、すべての式のグループ化が完全に記述されます。すべての式は、解析されるとおりにグループ化されなければいけません。前述の式は、次の方法でグループ化されます。
i = { {*++p + f()} + g() };
このコードでもなお「f() が g() よりも前に呼び出されなければならない」、あるいは、「g() が呼び出されるよりも前に p が増分されなければならない」ということはありません。
ISO C では、予想外のオーバーフローが発生しないように式を分割する必要があります。
ISO C では、不十分な理解と不正確な表現のために、括弧の信頼性と括弧に従った評価について、間違って記述されることがしばしばあります。
ISO C 式は解析によって指定されるグループ化を持つため、括弧は、式の解析方法を制御する手段としての役割しか果たしません。式の自然な優先度と結合規則が、括弧とまったく同じ重要性を持ちます。
前述の式は、グループ化や評価に対する効果を一切変えずに、次のように記述することもできました。
i = (((*(++p)) + f()) + g());
K&R C の再配置規則の理由を、次にいくつか示します。
再配置によって、より多くの最適化の機会が生まれること (たとえば、コンパイル時の定数折り畳み)
ほとんどのマシンにおいて、再配置によって整数型の式の結果が変わらないこと
すべてのマシンにおいて、いくつかの演算が数学的にも演算的にも交換可能で結合可能であること
ISO C 委員会は、記述される対象アーキテクチャーに適用されるときに、再配置規則は「as if」規則のインスタンスになるものであると、最終的に決定しました。ISO C の「as if」規則は、有効な C プログラムの動作を変更しないかぎり、実装が必要に応じて抽象マシン記述から離れることを一般的に許可しています。
したがって、すべてのビット単位の 2 項演算子 (シフトを除く) は任意のマシンで再配置できます。これは、そのような再グループ化は確認不可能であるためです。2 の補数を使用するマシンでオーバーフローが発生しない場合は、いくつかの理由のため、乗算または加算を含む整数式は再配置できます。