argcとargvのアドレスが12バイト離れているのはなぜですか?


40

私のコンピューター(Linuxを実行する64ビットIntel)で次のプログラムを実行しました。

#include <stdio.h>

void test(int argc, char **argv) {
    printf("[test] Argc Pointer: %p\n", &argc);
    printf("[test] Argv Pointer: %p\n", &argv);
}

int main(int argc, char **argv) {
    printf("Argc Pointer: %p\n", &argc);
    printf("Argv Pointer: %p\n", &argv);
    printf("Size of &argc: %lu\n", sizeof (&argc));
    printf("Size of &argv: %lu\n", sizeof (&argv));
    test(argc, argv);
    return 0;
}

プログラムの出力は

$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20

ポインタのサイズ&argvは8バイトです。私はのアドレスが期待argcされるようにしaddress of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8たが、4バイトのパディングは、それらの間にあります。これはなぜですか?

おそらくメモリアラインメントが原因である可能性がありますが、よくわかりません。

私が呼び出す関数でも同じ動作に気づきました。


15
何故なの?それらは174バイト離れている可能性があります。答えは、オペレーティングシステムや、のセットアップを行うラッパーライブラリによって異なりますmain
aschepler

2
@aschepler:のセットアップを行うラッパーに依存しないでくださいmain。Cではmain、通常の関数として呼び出すことができるため、通常の関数のように引数を受け取り、ABIに従う必要があります。
Eric Postpischil

@aschelper:他の関数でも同じ動作が見られます。
letmutx

4
これは興味深い「思考実験」ですが、実際には「なぜだろう」以上のものはありません。これらのアドレスは、OS、コンパイラ、コンパイラのバージョン、プロセッサアーキテクチャに応じて変化する可能性があり、「実際の生活」に依存するべきではありません。
ニール

2
sizeofの結果は%zu
phuclv

回答:


61

システムでは、最初のいくつかの整数またはポインター引数がレジスターで渡され、アドレスがありません。&argcまたは&argvでアドレスを取得する場合、コンパイラはレジスタの内容をスタックロケーションに書き込み、それらのスタックロケーションのアドレスを提供することにより、アドレスを作成する必要があります。そうすることで、コンパイラーは、ある意味で、スタックの場所が都合のよい場所を選択します。


6
これはスタックに渡された場合でも発生する可能性があることに注意してください。コンパイラは、値が入るローカルオブジェクトのストレージとしてスタックの着信値スロットを使用する義務はありません。これは、関数が最終的に末尾呼び出しを行う予定であり、末尾呼び出しの出力引数を生成するためにこれらのオブジェクトの現在の値が必要であることを意味します。
R .. GitHub ICE HELPING ICE

10

argcとargvのアドレスが12バイト離れているのはなぜですか?

言語標準の観点からは、答えは「特別な理由なし」です。Cは、関数パラメーターのアドレス間の関係を指定または暗示していません。@EricPostpischilは、特定の実装でおそらく何が起こっているかを説明しますが、それらの詳細は、すべての引数がスタックで渡される実装では異なります。これが唯一の選択肢ではありません。

さらに、そのような情報がプログラム内で役立つ方法を思い付くのに苦労しています。たとえば、のアドレスがのアドレスのargv12バイト前であることを「知っている」場合argcでも、これらのポインタの一方を他方から計算する定義された方法はまだありません。


7
@ R..GitHubSTOPHELPINGICE:一方から他方の計算は部分的に定義されていますが、十分に定義されていません。C標準は、変換先の方法に厳密ではなくuintptr_t、パラメーターのアドレス間の関係や引数が渡される場所を定義していません。
Eric Postpischil

6
@ R..GitHubSTOPHELPINGICE:往復できるという事実は、g(f(x))= xであることを意味します。ここで、xはポインター、fはconvert-pointer-to-uintptr_t、gはconvert-uintptr_t-toです。 -ポインター。数学的にも論理的にも、g(f(x)+4)= x + 4であることを意味しません。たとえば、f(x)がx²でg(y)がsqrt(y)の場合、g(f(x))= x(実際の非負のxの場合)ですが、g(f(x)+4) ≠一般的にx + 4。ポインターの場合、への変換によりuintptr_t、上位24ビットのアドレスと下位8ビットの一部の認証ビットが提供される場合があります。次に、4つを追加するだけで認証が失敗します。更新されません…
Eric Postpischil

5
…アドレスビット。または、uintptr_tへの変換により、上位16ビットにベースアドレスが与えられ、下位16ビットにオフセットが与えられ、下位ビットに4を加算すると、上位ビットに持ち込まれる可能性がありますが、スケーリングが正しくありません(表現されたアドレスがbase•65536 + offsetですが、一部のシステムのようにbase•64 + offsetです)。簡単に言うと、uintptr_t変換から得られるものは必ずしも単純な住所ではありません。
Eric Postpischil

4
@ R..GitHubSTOPHELPINGICE私の標準の読み取りから、に(void *)(uintptr_t)(void *)p等しい弱い保証しかありません(void *)p。そして、委員会がほぼこの正確な問題についてコメントしていることを指摘するのは価値があります。「実装...は、異なる起源に基づくポインタも、ビットが同一であっても別個のものとして扱う可能性がある」と結論付けています
Ryan Avella

5
@ R..GitHubSTOPHELPINGICE:申し訳ありませんが、uintptr_tポインタの違いや「既知の」距離のバイトではなく、アドレスの2つの変換の違いとして計算された値を追加していました。確かにそれは本当ですが、それはどのように役立ちますか?これは、回答の状態として「他からこれらのポインタのいずれかを計算するには、no定義された方法はまだあります」というのは本当のままですが、その計算は計算しないbからaではなく、計算bの両方からabするので、b量を計算するために引き算で使用する必要がありますたす。他からの計算は定義されていません。
Eric Postpischil
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.