%p 指定子は有効なポインタにのみ使用できますか? 質問する

%p 指定子は有効なポインタにのみ使用できますか? 質問する

私のプラットフォームにsizeof(int)==sizeof(void*)次のコードがあるとします:

printf( "%p", rand() );

の代わりに有効なポインタではない値を渡したため、これは未定義の動作になりますか%p?

ベストアンサー1

@larsmanの回答(制約に違反しているため、動作は未定義であると述べている)を拡張すると、実際のC実装は次sizeof(int) == sizeof(void*)のようになりますが、コードはprintf( "%p", (void*)rand() );

Motorola 68000プロセッサには、一般的な計算に使用される16個のレジスタがありますが、それらは同等ではありません。そのうちの8個(a0からa7)はメモリ(アドレスレジスタ)へのアクセスに使用され、残りの8個(d0からd7)は算術(データレジスタ)に使用されます。このアーキテクチャの有効な呼び出し規約は次のようになります。

  1. 最初の 2 つの整数パラメータを に渡しd0d1残りをスタックに渡します。
  2. 最初の 2 つのポインター パラメーターを に渡しa0a1残りをスタックに渡します。
  3. サイズに関係なく、他のすべての型をスタックに渡します。
  4. スタックに渡されるパラメータは、タイプに関係なく右から左にプッシュされます。
  5. スタックベースのパラメータは 4 バイト境界に揃えられます。

これは、多くの最新のプロセッサで使用されている呼び出し規約に似た、完全に合法的な呼び出し規約です。

たとえば、関数 を呼び出すには、とを に渡しvoid foo(int i, void *p)ます。id0pa0

関数 を呼び出すにはvoid bar(void *p, int i)、 と も渡す必要があるiことd0p注意してくださいa0

これらのルールでは、printf("%p", rand())は にフォーマット文字列を渡しa0、 に乱数パラメータを渡しますd0。一方、printf("%p", (void*)rand())は にフォーマット文字列を渡しa0、 にランダム ポインタ パラメータを渡しますa1

構造va_listは次のようになります。

struct va_list {
    int d0;
    int d1;
    int a0;
    int a1;
    char *stackParameters;
    int intsUsed;
    int pointersUsed;
};

最初の 4 つのメンバーは、レジスタの対応するエントリ値で初期化されます。は、、stackParametersおよびを介して渡される最初のスタックベースのパラメータを指し、それぞれ整数とポインタである名前付きパラメータの数に初期化されます。...intsUsedpointersUsed

このva_argマクロは、予想されるパラメータの型に基づいて異なるコードを生成するコンパイラの組み込みマクロです。

  • パラメータ型がポインターの場合は、va_arg(ap, T)に展開されます(T*)get_pointer_arg(&ap)
  • パラメータの型が整数の場合、va_arg(ap, T)に展開されます(T)get_integer_arg(&ap)
  • パラメータ タイプが他のタイプである場合は、va_arg(ap, T)に展開されます*(T*)get_other_arg(&ap, sizeof(T))

関数はget_pointer_arg次のようになります:

void *get_pointer_arg(va_list *ap)
{
    void *p;
    switch (ap->pointersUsed++) {
    case 0: p = ap->a0; break;
    case 1: p = ap->a1; break;
    case 2: p = *(void**)get_other_arg(ap, sizeof(p)); break;
    }
    return p;
}

関数はget_integer_arg次のようになります:

int get_integer_arg(va_list *ap)
{
    int i;
    switch (ap->intsUsed++) {
    case 0: i = ap->d0; break;
    case 1: i = ap->d1; break;
    case 2: i = *(int*)get_other_arg(ap, sizeof(i)); break;
    }
    return i;
}

関数はget_other_arg次のようになります。

void *get_other_arg(va_list *ap, size_t size)
{
    void *p = ap->stackParameters;
    ap->stackParameters += ((size + 3) & ~3);
    return p;
}

前述のように、 を呼び出すと、printf("%p", rand())にフォーマット文字列が渡されa0、 にランダムな整数が渡されますd0。しかし、printf関数が実行されると、 はフォーマットを認識し%p、 を実行し、の代わりに からパラメータをva_arg(ap, void*)使用して読み取ります。は初期化されていないため、ゴミが含まれています。生成したランダムな数値は無視されます。get_pointer_arga1d0a1

この例をさらに進めると、printf("%p %i %s", rand(), 0, "hello");次のように呼び出されます。

  • a0= フォーマット文字列のアドレス(最初のポインタパラメータ)
  • a1= 文字列のアドレス"hello"(2番目のポインタパラメータ)
  • d0= 乱数(最初の整数パラメータ)
  • d1= 0 (2番目の整数パラメータ)

関数が実行されると、期待どおりprintfに から書式文字列が読み取られます。 が検出されると、 からポインタが取得されて出力され、文字列 のアドレスが取得されます。次に が検出され、からパラメータが取得されるので、乱数が出力されます。最後に が検出され、スタックからパラメータが取得されます。しかし、スタックにパラメータが渡されていません。これにより、未定義のスタック ガベージが読み取られ、文字列ポインタであるかのように出力しようとすると、プログラムがクラッシュする可能性が高くなります。a0%pa1"hello"%id0%s

おすすめ記事