20

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

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

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

4

4 に答える 4

20

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

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

  1. 最初の 2 つの整数パラメータをd0andに渡しますd1。残りをスタックに渡します。
  2. 最初の 2 つのポインター パラメーターをa0およびに渡しますa1。残りをスタックに渡します。
  3. サイズに関係なく、スタック上の他のすべての型を渡します。
  4. スタックに渡されたパラメーターは、型に関係なく右から左にプッシュされます。
  5. スタックベースのパラメーターは、4 バイト境界で整列されます。

これは、最新の多くのプロセッサで使用されている呼び出し規則と同様に、完全に正当な呼び出し規則です。

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

関数を呼び出すには、inとinvoid bar(void *p, int i)も渡すことに注意してください。id0pa0

これらのルールの下で、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_argして読み取ります。初期化していないのでゴミが入っています。生成した乱数は無視されます。a1d0a1

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

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

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

于 2012-07-27T14:28:49.487 に答える
13

C 標準、7.21.6.1、fprintf関数は、次のように述べています

p 引数は へのポインタでなければなりませんvoid

付録 J.2 により、これは制約であり、制約に違反すると UB が発生します。

(以下は、なぜこれが UB であるべきかという私の以前の理由です。これは複雑すぎました。)

void*そのパラグラフはから を取得する方法を説明していません...が、C 標準自体がこの目的のために提供している唯一の方法は 7.16.1.1 ですva_arg

が実際の次の引数の型と互換性がない場合(デフォルトの引数の昇格に従って昇格されるため)、動作は未定義です

6.2.7 互換型と複合型を読んだ場合、サイズに関係なく、互換性があり、互換性があるべきであるというヒントはvoid*ありません。intしたがって、標準 Cva_argで実装する唯一の方法であるため、動作は未定義です。printf

于 2012-07-27T13:11:08.340 に答える
5

はい、未定義です。C++11、3.7.4.2/4 から:

無効なポインター値を使用した場合 (解放関数に渡すことを含む) の影響は未定義です。

脚注付き:

一部の実装では、システム生成のランタイム エラーが発生します。

于 2012-07-27T13:04:21.050 に答える
-2

%p は、printf の単なる出力形式指定です。型がポインターでない場合、一部のコンパイラーは警告を発行しますが、ポインターを逆参照または検証する必要はありません。

int main(void)
{
    int t = 5;
    printf("%p\n", t);
}

コンパイル警告:

warning: format ‘%p’ expects argument of type ‘void*’, but argument 2 has type ‘int’ [-Wformat]

出力:

0x5
于 2012-07-27T13:06:22.800 に答える