一部のマシンでlong intに12バイトかかるのはなぜですか?


26

私のマシンでこのコードをコンパイルした後、奇妙なことに気付きました:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

結果は次のとおりです。すべてのintアドレスの間に4バイトの違いがあることに注意してください。ただし、最後のintとlong intの間には、12バイトの違いがあります。

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88

3
ソースコードに別のinthに置きます。コンパイラは、前に、ギャップにそれを置くことができhます。
ctrl-alt-delor

32
サイズを決定するためにメモリアドレスの違いを使用しないでください。そのためのsizeof機能があります。printf("size: %d ", sizeof(long));
クリスシュナイダー

10
でアドレスの下位4バイトのみを印刷しています%x。幸運なことに、プラットフォームで正しく動作してunsigned int、期待するフォーマット文字列でポインタ引数を渡しますが、多くのABIではポインタとintのサイズが異なります。%pポータブルコードでポインターを印刷するために使用します。(コードが、8つすべての下半分ではなく、最初の4つのポインターの上半分と下半分を印刷するシステムを想像するのは簡単です。)
Peter Cordes

5
@ChrisSchneider size_tの使用を印刷します%zu。@yoyo_fun はアドレスを出力します%p。間違った書式指定子を使用すると、呼び出し、未定義の動作
phuclvに

2
@luuは誤った情報を広めません。まともなコンパイラーは、変数がCで宣言される順序を気にしません。気にするなら、それがあなたが記述する方法でそれをする理由はありません。
gnasher729

回答:


81

12バイトは必要ありませんでしたが、8バイトしか必要ありませんでした。ただし、このプラットフォームでの8バイト長のintのデフォルトのアライメントは8バイトです。そのため、コンパイラはlong intを8で割り切れるアドレスに移動する必要がありました。「明白な」アドレスda54dc8cは8で割り切れないため、12バイトのギャップがあります。

これをテストできるはずです。longの前に別のintを追加すると、そのうち8個が存在するため、long intは移動せずに正常に整列されることがわかります。これで、前のアドレスからわずか8バイトになります。

このテストは機能するはずですが、このように構成された変数に依存するべきではないことを指摘する価値があるでしょう。ACコンパイラーは、変数の並べ替えを含む(いくつかの注意事項を含む)プログラムを迅速に実行しようとするために、あらゆる種類のファンキーなことを実行できます。


3
差ではなく、ギャップ。
デデュプリケーター

10
「変数の並べ替えを含む」。同時に2つの変数を使用しないとコンパイラが判断した場合、それらを部分的に重複または完全にオーバーレイすることも自由です。
Roger Lipscombe

8
または、実際には、スタック上ではなくレジスタ内に保持します。
停止ハーミングモニカ

11
@OrangeDogアドレスがこの場合のように取得された場合、それが起こるとは思いませんが、一般的には、もちろん正しいです。
アレックス

5
@Alex:アドレスを取得するときに、メモリとレジスタを使用して面白いものを取得できます。アドレスを取得するということは、メモリの場所を指定する必要があるということですが、実際に使用する必要があるという意味ではありません。アドレスを取得し、それに3を割り当てて別の関数に渡すと、RDIに3を書き込んで呼び出すだけで、メモリには決して書き込まれません。ときどきデバッガで驚く。
ザンリンクス

9

これは、コンパイラーが変数間に追加のパディングを生成して、メモリー内で変数が正しく整列されるようにするためです。

ほとんどの最新のプロセッサでは、値にそのサイズの倍数のアドレスがある場合、値にアクセスする方が効率的です。h最初に利用可能な場所に置いていた場合、そのアドレスは0xda54dc8cでしたが、これは8の倍数ではないため、使用するのに効率的ではありませんでした。コンパイラはこれを認識しており、最後の2つの変数の間に未使用のスペースを少し追加して、それが確実に行われるようにします。


説明してくれてありがとう。サイズの倍数である変数へのアクセスがより効率的である理由に関するいくつかの資料を教えていただけますか?なぜこれが起こっているのか知りたいのですが?
yoyo_fun

4
あなたがあれば@yoyo_funと 本当にメモリを理解したい、その後、対象の有名な論文がありfuturetech.blinkenlights.nl/misc/cpumemory.pdfは
アレックス・

1
@yoyo_funとても簡単です。一部のメモリコントローラーは、プロセッサのビット幅の倍数にしかアクセスできません(たとえば、32ビットプロセッサはアドレス0〜3、4〜7、8〜11などのみを直接要求できます)。位置合わせされていないアドレスを要求する場合、プロセッサは2つのメモリ要求を行ってからデータをレジスタに取得する必要があります。したがって、32ビットに戻り、アドレス1に値を保存する場合、プロセッサはアドレス0〜3、4〜7を要求し、1、2、3、および4からバイトを取得する必要があります。メモリ読み取りが無駄になりました。
phyrfox

2
軽微な点ですが、メモリアクセスのミスアライメントは、パフォーマンスヒットではなく回復不能な障害になる可能性があります。アーキテクチャ依存。
ジョンチェスターフィールド

1
@JonChesterfield-はい。だからこそ、私が述べた説明はほとんどの最新のアーキテクチャ(x86とARMを主に意味する)に適用されるとコメントしました。さまざまな方法で動作する他のものもありますが、それらはかなり一般的ではありません。(興味深いことに:ARMを使用整列アクセスを必要アーキテクチャの一つであることを、彼らは、後のリビジョンにアラインされていないアクセスの自動処理を追加)
ジュール

2

これらのローカル変数のアドレスを相互に関連付けるための言語の要件がないため、テストは必ずしも考えているものをテストしているわけではありません。

ストレージ割り当てについて何かを推測できるようにするには、これらを構造体のフィールドとして配置する必要があります。

ローカル変数は、特定の方法でストレージを隣接して共有する必要はありません。コンパイラは、スタック内の任意の場所に一時変数を挿入できます。たとえば、これらのローカル変数の任意の2つの間にある可能性があります。

対照的に、一時変数を構造体に挿入することは許可されないため、代わりに構造体フィールドのアドレスを出力すると、同じメモリの論理チャック(構造体)から割り当てられたアイテムを比較することになります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.