59

gcc-strict-aliasing-and-casting-through-a-unionで、ポインターを介したユニオンのしゃれで問題が発生したかどうかを尋ねました。これまでのところ、答えは「いいえ」のようです。

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

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

「厳密なエイリアシング規則は、[標準化]時代の初めからCおよびC++に存在していた標準の一部に根ざしています。別の型の左辺値を介して1つの型のオブジェクトにアクセスすることを禁止する条項は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

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

繰り返しになりますが、あなた自身のホラーストーリーはありますか?もちろん、によって示されていない問題が優先されます。-Wstrict-aliasingまた、他のCコンパイラも歓迎します。

6月2日追加: Michael Burrの回答の最初のリンクは、確か​​にホラーストーリーと見なされますが、おそらく少し古いものです(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コピーされる)と不満を漏らしています。

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

_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;

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

4

6 に答える 6

34

私自身のホラーストーリーはありませんが、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:10PM+0100に、HorstvonBrandは次のように書いています。

Jean Tourrilhes <>は言った:

私にはコンパイラのバグのように見えます...次のコードが-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とstructiw_event*iweがメモリの別々の領域を指していると自由に想定できます。

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

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

そして、上記に関するLinus Torvaldのコメント:

Jean Tourrilhesは次のように書いています:>

私にはコンパイラのバグのように見えます...

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

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

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

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

私は数年前に正気を取り戻そうとしましたが、gcc開発者はこの分野の現実の世界を本当に気にしませんでした。すでに見た返事から判断すると、それが変わったらびっくりします。

私はそれと戦うためにわざわざするつもりはありません。

ライナス

http://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg01647.html

タイプベースのエイリアシングは愚かです。それはとてもばかげているので、面白くもありません。壊れている。そして、gccは壊れた概念を採用し、それを意味のない「法の手紙による」ものにすることで、よりそうしました。

..。

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

unsigned long a;

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

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

于 2010-06-02T16:09:47.697 に答える
7

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;
}
于 2011-09-22T17:19:04.293 に答える
5

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の問題を再現できないため、修正されている可能性があります。いくつかの回避策:

  • __attribute__((noinline))copy()に使用します。
  • gccスイッチを使用し-fno-strict-aliasingます。
  • copy()の3番目のパラメーターをからb[][n]に変更しますb[][2]
  • -O2またはを使用しないでください-O3

その他の注意事項:

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

更新上記はOPの質問に実際には答えていません。なぜなら、彼(つまり私)は厳密なエイリアシングが「合法的に」コードを壊した場合について質問していたのに対し、上記は単なるガーデンバラエティコンパイラのバグのようです。

私はそれをGCCBugzillaに報告しましたが、それが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

于 2011-06-03T21:20:23.177 に答える
3

ここに私のものがあります:

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

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

このバグは、古いバージョンのGCCと古いバージョンの特定のライブラリを使用した特定のプラットフォームでのみ発生しました。そして、-O2をオンにした場合のみ。-fno-strict-aliasingはそれを解決しました。

于 2011-12-17T18:54:08.283 に答える
2

CのCommonInitialSequenceルールは、一致するタイプの要素で始まる場合に、さまざまな構造タイプの先頭部分で機能する関数を記述できるようにするものとして解釈されていました。C99では、関連する構造タイプが同じユニオンのメンバーであり、使用時に完全な宣言が表示されている場合にのみ適用されるように、ルールが変更されました。

gccの作成者は、次の事実にもかかわらず、問題の言語は、アクセスが共用体型を介して実行される場合にのみ適用可能であると主張しています。

  1. 共用体型を介してアクセスを実行する必要がある場合は、完全な宣言を表示する必要があることを指定する理由はありません。

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

于 2016-08-19T22:29:27.430 に答える
1

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

int main()
{
  int v = 10;

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

  s->v = 1;

  return v;
}
于 2010-10-08T20:04:33.070 に答える