1

Cコンパイラ(gccとclangを試しました)が関数の引数リストを内部でどのように編成するのか疑問に思いました。この目的のために、たとえば次の2つのファイルで構成されるプログラムを作成しました。

foo.c:

double foo (double (*f) (void *, double), void * arg, double x)
{
    return f (arg, x);
}

main.c:

#include <stdio.h>

extern double foo (double (*) (double), double);

double bar (double x)
{   
    return x + x;
}

int main ()
{   
    double x = foo (&bar, 2);
    printf ("%g\n", x);
    return 0;
}

foofoo.cの引数リストと本体で遊んだ。foo.cがmain.cの定義と一致しない場合、プログラムの動作はC標準に従って未定義である必要があります(私が正しく理解している場合)。

私はいくつかのバリエーション(上記のものを含む)を試しましたが、プログラムは4を出力します。よりエキゾチックなものの1つは

double foo (double x, double (*f) (char, double, int), int r)
{
    char a;
    return f (a, x, r);
}

。しかし、私が次のようなことを試してみると

double foo (double x, double (*f) (char, double, double), double y)
{
    char a;
    return f (a, y, x);
}

結果は4にはなりませんが、f(a, x, y)代わりに書くと、再び4になります。

実験では、引数リストは、さまざまなタイプの引数の順序に関する情報が失われた、さまざまなタイプに対応する配列のリストとして内部的に表されていると思います。たとえば、引数リスト (char a, double x, int i, double y, char b)は次のよう(char:{a, b}, int:{i}, double:{x, y})になり、へのキャストは(double z, char c)に等しくなります。ここで、型の配列として(char:{c = a}, double:{z = x})定義しました。T:{}T

それで:

私の解釈は正しいですか?

この動作はどこかで標準化されていますか?

そのような振る舞いにどれだけ頼ることができますか?

この動作により、いくつかのジェネリックプログラミングが可能になります。誰かが実際にそれを悪用しますか?

ありがとう!

4

1 に答える 1

3

関数のシグネチャが一致しない場合、どの動作にも依存できません。プログラムは正常に動作し、クラッシュしたり、ロケット ミサイルを発射したりできます。これは、未定義の動作の定義です。

variadict 関数、またはパラメーターを記述しない関数 (つまり、f()、f(void) と混同しないでください) を使用する場合にのみ、何かに依存することができます。それらについては、引数の昇格と変換に関する規則があります。詳細については、C99 標準を探してください。

残りは純粋な憶測です。プロセッサのアセンブル言語がどのように機能するかを知っていれば、いくつかの動作を観察して推測することができます。たとえば x86 では、関数を呼び出してパラメーターを渡す標準的な方法が明確に定義されています...

いずれにせよ、未定義の動作に依存するのは得策ではありません。

于 2013-01-17T13:54:19.943 に答える