スタック変数はGCCによって整列されていますか__attribute __((aligned(x)))?


88

私は次のコードを持っています:

#include <stdio.h>

int
main(void)
{
        float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

そして、私は次の出力を持っています:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

なぜの住所 a[0]の倍数ではないは0x1000ですか?

正確には __attribute__((aligned(x)))は何ですか?私はこの説明を誤解しましたか?

gcc4.1.2を使用しています。

回答:


98

問題は、配列がスタック上にあり、コンパイラが古すぎて、過剰に整列されたスタック変数をサポートできないことだと思います。GCC 4.6以降では、そのバグが修正されました

C11 / C ++ 112alignas(64) float a[4];アラインメントの任意の累乗で機能します。使用して
いたGNUCも__attribute__((aligned(x)))同様です。

(C11では#include <stdalign.h>#define alignas _Alignascpprefの場合)。


しかし、4kページ境界への非常に大きな配置の場合、スタックに配置したくない場合があります。

関数の開始時にスタックポインタは何でもかまいません。必要以上に割り当てて調整しない限り、配列を整列させる方法はありません。(コンパイラはand rsp, -4096、割り当てられた0〜4088バイトのいずれかを使用するか、同等であり、そのスペースが十分に大きいかどうかで分岐することは可能ですが、配列または他のローカルのサイズよりもはるかに大きい巨大なアラインメントのために実行されません。通常の場合ではありません。)

配列を関数からグローバル変数に移動すると、機能するはずです。あなたができる他のことはそれをローカル変数として保持することです(これは非常に良いことです)が、それを作りますstatic。これにより、スタックに保存されなくなります。配列のコピーは1つしかないため、これらの方法はどちらもスレッドセーフでも再帰セーフでもないことに注意してください。

このコードで:

#include <stdio.h>

float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

私はこれを得る:

0x804c000 0x804c004 0x804c008 0x804c00c

これが期待されていることです。元のコードでは、あなたと同じようにランダムな値を取得します。


11
+1正解。別の解決策は、ローカル配列を静的にすることです。スタック上の整列は常に問題であり、それを回避する習慣を身につけることが最善です。
ダン・オルソン

そうそう、私はそれを静的にすることを考えていませんでした。名前の衝突を防ぐので、これは良い考えです。答えを編集します。
Zifre 2009年

3
静的にすると、再入可能でなく、スレッドセーフでもなくなることに注意してください。
ArchaeaSoftware 2013

3
また、gcc 4.6+は、スタック上でもこれを正しく処理します。
textshell 2015

1
この答えは以前は正しかったが、今はそうではない。4.6と同じくらい古いgccは、おそらく古いものであり、スタックポインタを整列させて、alignas(64)自動ストレージを備えたオブジェクトにC11 / C ++ 11などを正しく実装する方法を知っています。そしてもちろん、GNU C__attribute((aligned((64)))
PeterCordes19年

41

属性を引き起こすgccにバグがありましたがスタック変数で機能しないように調整されていました。以下にリンクされているパッチで修正されているようです。以下のリンクには、この問題に関するかなりの議論も含まれています。

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660

上記のコードを2つの異なるバージョンのgccで試しました。RedHat5.7ボックスの4.1.2ですが、問題と同様に失敗しました(ローカル配列が0x1000バイトの境界に整列していませんでした)。次に、RedHat6.3でgcc4.4.6を使用してコードを試しましたが、問題なく動作しました(ローカル配列が整列されていました)。Myth TVの人々も同様の問題を抱えていました(上記のgccパッチで修正されたようです)。

http://code.mythtv.org/trac/ticket/6535

とにかく、gccにバグが見つかったようですが、それは後のバージョンで修正されているようです。


3
リンクされたバグによると、gcc 4.6は最初のリリースであり、この問題はすべてのアーキテクチャで完全に修正されています。
textshell 2015

それに加えて、スタック上に整列された変数を作成するためにgccによって生成されたアセンブリコードは非常に恐ろしく、最適化されていません。それで、呼び出す代わりにスタックに整列された変数を割り当てることは意味がありmemalign()ますか?
ジェローム・Pouiller

13

最近のGCC(4.5.2-8ubuntu4でテスト済み)は、アレイが適切に配置された状態で期待どおりに機能しているようです。

#include <stdio.h>

int main(void)
{
    float a[4] = { 1.0, 2.0, 3.0, 4.0 };
    float b[4] __attribute__((aligned(0x1000))) = { 1.0, 2.0, 3.0, 4.0 };
    float c[4] __attribute__((aligned(0x10000))) = { 1.0, 2.0, 3.0, 4.0 };

    printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
    printf("%p %p %p %p\n", &b[0], &b[1], &b[2], &b[3]);
    printf("%p %p %p %p\n", &c[0], &c[1], &c[2], &c[3]);
}

私は得る:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c

配列がスタックに割り当てられていることを考えると、これは少し驚くべきことです-スタックが穴でいっぱいになったことを意味しますか?
ysap 2012年

または、彼のスタックは16バイトに揃えられています。
user7116 2013年

9

位置合わせはすべてのタイプに有効というわけではありません。構造を使用して、実際の属性を確認することを検討する必要があります。

#include <stdio.h>

struct my_float {
        float number;
}  __attribute__((aligned(0x1000)));

struct my_float a[4] = { {1.0}, {2.0}, {3.0}, {4.0} };

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

そして、あなたは読むでしょう:

0x603000 0x604000 0x605000 0x606000

それはあなたが期待していたものです。

編集: @yzapによってプッシュされ、@ Calebケースのコメントに続いて、最初の問題はGCCバージョンのみが原因です。リクエスターのソースコードを使用して、GCC3.4.6とGCC4.4.1を確認しました。

$ ./test_orig-3.4.6
0x7fffe217d200 0x7fffe217d204 0x7fffe217d208 0x7fffe217d20c
$ ./test_orig-4.4.1
0x7fff81db9000 0x7fff81db9004 0x7fff81db9008 0x7fff81db900c

古いGCCバージョン(4.4.1より前のどこか)がアライメントの病状を示していることは今や明らかです。

注1:提案されたコードは、「配列の各フィールドの整列」として理解した質問に答えていません。

注2:非静的a []をmain()内に持ち込み、GCC 3.4.6でコンパイルすると、構造体の配列の整列ディレクティブが壊れますが、構造体間の距離は0x1000に保たれます...それでも悪いです!(回避策については、@ zifreの回答を参照してください)


2
zifreが答えたように、それはタイプではなく、バージョンで静的にしたという事実です。
ysap 2012年

@ysap、それを機能させたのはGCCバージョンとグローバル定義の両方でした。コメントありがとうございます!私はそれを修正するために答えを編集しました。:)
levif 2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.