web-dev-qa-db-ja.com

gcc、厳格なエイリアシング、ホラーストーリー

gcc-strict-aliasing-and-casting-through-a-union では、ユニオンポインターによるパンニングで問題が発生したかどうかを確認しました。これまでのところ、答えはNoのようです。

この質問はより広いです:あなたはgccと厳格なエイリアシングについてホラーストーリーを持っていますか?

背景: c99-strict-aliasing-rules-in-c-gccのAndreyTの回答 からの引用:

「厳格なエイリアシングルールは、[標準化された]時代の初めからCおよびC++に存在していた標準の一部に根ざしています。あるタイプの左辺値を介してあるタイプのオブジェクトにアクセスすることを禁止する句は、C89/90(6.3 )だけでなく、C++ 98(3.10/15)でも...すべてのコンパイラが強制したり、それに依存したりすることを望んだ(または敢えて)とは限りません。」

さて、gccは現在、その-fstrict-aliasingスイッチ。そして、これはいくつかの問題を引き起こしています。たとえば、Mysqlのバグについての優れた記事 http://davmac.wordpress.com/2009/10/ と、同様に優れた議論 http:// cellperformance]を参照してください。 beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

その他の関連性の低いリンク:

繰り返しますが、あなた自身のホラーストーリーはありますか?問題notで示される-Wstrict-aliasingはもちろん推奨されます。また、他のCコンパイラも歓迎します。

6月2日に追加Michael Burr's answer の最初のリンク確かにはホラーストーリーとみなされますが、おそらく少し古い(2003年から)です。簡単なテストを行いましたが、問題は明らかに解消されました。

ソース:

#include <string.h>
struct iw_event {               /* dummy! */
    int len;
};
char *iwe_stream_add_event(
    char *stream,               /* Stream of events */
    char *ends,                 /* End of stream */
    struct iw_event *iwe,       /* Payload */
    int event_len)              /* Real size of payload */
{
    /* Check if it's possible */
    if ((stream + event_len) < ends) {
            iwe->len = event_len;
            memcpy(stream, (char *) iwe, event_len);
            stream += event_len;
    }
    return stream;
}

具体的な苦情は次のとおりです。

一部のユーザーは、[上記]コードが-fno-strict-aliasingなしでコンパイルされると、書き込みとmemcpyの順序が逆になる(つまり、偽のlenがストリームにmemコピーされる)と不平を言っています。

gcc4.3.4をCYGWINで-O3を使用してコンパイルしたコード(間違っている場合は修正してください-アセンブラが少し錆びています!):

_iwe_stream_add_event:
        pushl       %ebp
        movl        %esp, %ebp
        pushl       %ebx
        subl        $20, %esp
        movl        8(%ebp), %eax       # stream    --> %eax
        movl        20(%ebp), %edx      # event_len --> %edx
        leal        (%eax,%edx), %ebx   # sum       --> %ebx
        cmpl        12(%ebp), %ebx      # compare sum with ends
        jae L2
        movl        16(%ebp), %ecx      # iwe       --> %ecx
        movl        %edx, (%ecx)        # event_len --> iwe->len (!!)
        movl        %edx, 8(%esp)       # event_len --> stack
        movl        %ecx, 4(%esp)       # iwe       --> stack
        movl        %eax, (%esp)        # stream    --> stack
        call        _memcpy
        movl        %ebx, %eax          # sum       --> retval
L2:
        addl        $20, %esp
        popl        %ebx
        leave
        ret

マイケルの回答の2番目のリンクについては、

*(unsigned short *)&a = 4;

gccは通常(常に?)警告を出します。しかし、これに対する有効な解決策gcc)を使用するを信じる

#define CAST(type, x) (((union {typeof(x) src; type dst;}*)&(x))->dst)
// ...
CAST(unsigned short, a) = 4;

私はSOこれが gcc-strict-aliasing-and-casting-through-a-union で問題ないかどうか尋ねましたが、今のところ誰も反対していません。

53
Joseph Quinsey

私自身のホラーストーリーはありませんが、ここにLinus Torvaldsからの引用があります(これらが質問内のリンクされた参照の1つにすでにある場合は申し訳ありません)。

http://lkml.org/lkml/2003/2/26/158

日付水2003年2月26日09:22:15 -0800件名Re:無効なコンパイル-fno-strict-aliasingなしJean Tourrilhes <>

2003年2月26日水曜日の04:38:10 PM +0100に、ホルストフォンブランドは次のように書いています。

ジャントゥリレス<>氏:

コンパイラのバグのようです... -fno-strict-aliasingなしで次のコードをコンパイルすると、書き込みとmemcpyの順序が逆になる(一部の偽のlenがmemコピーされていることを意味します)ストリームに)。コード(linux/include/net/iw_handler.hから):

static inline char *
iwe_stream_add_event(char *   stream,     /* Stream of events */
                     char *   ends,       /* End of stream */
                    struct iw_event *iwe, /* Payload */
                     int      event_len)  /* Real size of payload */
{
  /* Check if it's possible */
  if((stream + event_len) < ends) {
      iwe->len = event_len;
      memcpy(stream, (char *) iwe, event_len);
      stream += event_len;
  }
  return stream;
}

私見、コンパイラは、並べ替えが危険であることを知るのに十分なコンテキストを持っている必要があります。この単純なコードをさらに弾力性のあるものにする提案があれば歓迎します。

コンパイラは、厳密なエイリアシングにより、char * streamおよびstruct iw_event * iweがメモリの個別の領域をポイントすることを自由に想定できます。

どちらが真実で、どれが私が不満を言っている問題ではありません。

(後から注意:このコードは問題ありませんが、memcpyのLinuxの実装 long * にキャストするマクロは、より大きなチャンクでコピーします。正しく定義されたmemcpygcc -fstrict-aliasingはこのコードを解読することは許可されていませんが、コンパイラがバイトコピーをどのように変換するかわからない場合は、カーネルを定義するためにインラインasmが必要ですmemcpy効率的なasmにループします。これはgccの前のgccの場合でした7)

上記についてのLinus Torvaldのコメント:

Jean Tourrilhesが書いた:>

コンパイラのバグのようです...

なぜカーネルは「-fno-strict-aliasing」を使用すると思いますか?

Gccの人々は、実際に作業を行うことよりも、c99仕様によって何が許可されるかを見つけることに関心があります。特にエイリアシングコードは有効にする価値すらありません、何かがエイリアシングできるときをgccに正気に伝えることは不可能です。

次のコードが-fno-strict-aliasingなしでコンパイルされると、書き込みとmemcpyの順序が逆になる(一部の偽のlenがストリームにmemコピーされることを意味する)との不満を言うユーザーもいます。

「問題」とは、memcpy()をインライン化することです。この時点で、gccはエイリアスできるという事実を気にしません。そのため、gccはすべてを並べ替えて、それ自体が原因であると主張します。私たちがgccにそれを告げるための正気な方法はありませんが。

私は数年前に健全な方法を試みました、そしてgcc開発者は本当にこの分野の現実の世界を気にしませんでした。私がすでに見た返信から判断すると、それが変わっていたら驚きます。

戦うつもりはありません。

ライナス

http://www.mail-archive.com/[email protected]/msg01647.html

タイプベースのエイリアスはstupidです。信じられないほど愚かで、面白くもありません。壊れてます。そしてgccは壊れた概念を取り入れ、それを意味のない「法規による」ものにすることで、それをより明確にしました。

...

私は事実について、gccが明らかに(静的に)同じアドレスへの書き込みアクセスを並べ替えることを知っています。 Gccは突然、

unsigned long a;

a = 5;
*(unsigned short *)&a = 4;

最初に4に設定するように並べ替えることができます(明確にエイリアスを設定しないため、標準を読み取ることにより)。次に、a = 5の割り当てが後であったため、4の割り当てを完全に省略できます。そして、誰かがコンパイラーが正気でないと不平を言うと、コンパイラーの人々は「ニャア、ニャーア、標準は私たちがこれを行うことができると言った」と言うでしょう、それがSENSEを作ったかどうか尋ねる完全な内省はありません。

31
Michael Burr

SWIGは、厳密なエイリアスがオフになっていることに依存するコードを生成します。これにより、 あらゆる種類の問題 が発生する可能性があります。

SWIGEXPORT jlong JNICALL Java_com_mylibJNI_make_1mystruct_1_1SWIG_12(
       JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2) {
  jlong jresult = 0 ;
  int arg1 ;
  int arg2 ;
  my_struct_t *result = 0 ;

  (void)jenv;
  (void)jcls;
  arg1 = (int)jarg1; 
  arg2 = (int)jarg2; 
  result = (my_struct_t *)make_my_struct(arg1,arg2);
  *(my_struct_t **)&jresult = result;              /* <<<< horror*/
  return jresult;
}
7
paleozogt

gcc、エイリアス、および2次元可変長配列:次のサンプルコードは、2x2行列をコピーします。

_#include <stdio.h>

static void copy(int n, int a[][n], int b[][n]) {
   int i, j;
   for (i = 0; i < 2; i++)    // 'n' not used in this example
      for (j = 0; j < 2; j++) // 'n' hard-coded to 2 for simplicity
         b[i][j] = a[i][j];
}

int main(int argc, char *argv[]) {
   int a[2][2] = {{1, 2},{3, 4}};
   int b[2][2];
   copy(2, a, b);    
   printf("%d %d %d %d\n", b[0][0], b[0][1], b[1][0], b[1][1]);
   return 0;
}
_

CentOSのgcc4.1.2では、次のようになります。

_$ gcc -O1 test.c && a.out
1 2 3 4
$ gcc -O2 test.c && a.out
10235717 -1075970308 -1075970456 11452404 (random)
_

これが一般的に知られているかどうかはわかりませんが、バグなのか機能なのかはわかりません。 Cygwinのgcc4.3.4で問題を再現できないため、修繕。いくつかの回避策:

  • Copy()には__attribute__((noinline))を使用します。
  • Gccスイッチ_-fno-strict-aliasing_を使用します。
  • Copy()の3番目のパラメーターを_b[][n]_から_b[][2]_に変更します。
  • _-O2_または_-O3_は使用しないでください。

その他のメモ:

  • これは私自身の質問に対する1年と1日後の回答です(そして、他に2つの回答しかないことに少し驚いています)。
  • 実際のコードであるカルマンフィルターでは、これで数時間を失いました。一見小さな変更は、おそらくgccの自動インライン化の変更が原因で、劇的な影響を及ぼします(これは推測です;私はまだ不確かです)。しかし、恐らくホラーストーリーとは見なされません。
  • はい、私はあなたがこのようなcopy()を書かないことを知っています。 (そして、余談ですが、gccがダブルループを展開しなかったことに少し驚きました。)
  • _-Wstrict-aliasing=_を含むgcc警告スイッチは、ここでは何もしませんでした。
  • 1次元の可変長配列は問題ないようです。

Update上記は、OPの質問に実際には回答しません。厳密なエイリアシングが「合法的に」あなたのコードを壊した場合について、一方、上記は庭園の多様なコンパイラのバグのようです。

私はそれを GCC Bugzilla に報告しましたが、10億ドルのRHEL5の鍵であると私は信じていますが、古い4.1.2には興味がありませんでした。 4.2.4以降では発生しません。

また、マトリックスが1つだけの、同様のバグのやや単純な例があります。コード:

_static void zero(int n, int a[][n]) {
   int i, j;
   for (i = 0; i < n; i++)
   for (j = 0; j < n; j++)
      a[i][j] = 0;
}

int main(void) {
   int a[2][2] = {{1, 2},{3, 4}};
   zero(2, a);    
   printf("%d\n", a[1][1]);
   return 0;
}
_

結果を生成します:

_gcc -O1 test.c && a.out
0
gcc -O1 -fstrict-aliasing test.c && a.out
4
_

バグの原因は_-fstrict-aliasing_と_-finline_の組み合わせのようです。

5
Joseph Quinsey

これが私のものです:

http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html

CADプログラム内の特定の図形が正しく描画されませんでした。回帰テストスイートの作成に取り組んでいるプロジェクトのリーダーに感謝します。

バグは特定のプラットフォームでのみ現れ、GCCの古いバージョンと特定のライブラリの古いバージョンがありました。次に、-O2をオンにしてのみ。 -fno-strict-aliasingが解決しました。

2
don bright

Cの共通初期シーケンスルールは、さまざまな構造タイプの先頭部分で機能する関数を記述できるように解釈されていました。ただし、それらが一致するタイプの要素で始まっている場合に限ります。 C99では、関係する構造体タイプが、使用時にcomplete宣言が可視である同じ共用体のメンバーである場合にのみ適用されるように、ルールが変更されました。

Gccの作者は、問題の言語は、アクセスがunionタイプを介して実行される場合にのみ適用可能であると主張しています。

  1. ユニオンタイプを介してアクセスを実行する必要がある場合、complete宣言を表示する必要があることを指定する理由はありません。

  2. CISの規則はユニオンの観点から説明されていましたが、その主な有用性は、構造体が配置されてアクセスされる方法について暗黙に示したものにありました。 S1とS2がCISを共有する構造である場合、S1とS2へのポインターを外部ソースから受け入れた関数が、C89のCISルールに準拠することができず、同じ動作が実際には共用体オブジェクト内になかった構造。したがって、構造体に対するCISサポートの指定は、それがすでに共用体に対して指定されている場合、冗長になります。

2
supercat

次のコードは、gcc 4.4.4で10を返します。 unionメソッドまたはgcc 4.4.4に何か問題がありますか?

int main()
{
  int v = 10;

  union vv {
    int v;
    short q;
  } *s = (union vv *)&v;

  s->v = 1;

  return v;
}
2
user470617