1

現在、アプリケーション内の関数クラスの関数シグネチャを変更しています。これらの関数は関数テーブルに格納されているため、この関数テーブルも変更することを期待していました。特定のインスタンスでは、新しい関数シグネチャを既に使用していることに気付きました。ただし、すべてが関数テーブルに配置されるときに正しい関数型にキャストされるため、警告は発生しません。

関数が呼び出されると、実際には関数宣言の一部ではない追加のパラメーターが渡されますが、それらはパラメーター リストの最後にあります。

関数パラメーターが C で渡される方法によってこれが保証されているかどうかは判断できません。sprintf のような可変引数関数を実行するには、パラメーター リストの最後にあるものが何であれ、以前の引数を正しく解決できる必要があります。 ?

複数のプラットフォームで問題なく機能することは明らかですが、好奇心から、どのように、そしてなぜ機能するのか知りたいです。

4

2 に答える 2

3

ただし、すべてが関数テーブルに配置されるときに正しい関数型にキャストされるため、警告は発生しません。

したがって、コンパイラは何の役にも立ちません。C プログラマーはキャストしすぎます。>_<

関数パラメーターが C で渡される方法によってこれが保証されているかどうかは判断できません。sprintf のような可変引数関数を実行するには、パラメーター リストの最後にあるものが何であれ、以前の引数を正しく解決できる必要があります。 ?

技術的には、未定義の動作があります。ただし、プラットフォームが標準の C 呼び出し規則 (Scott の回答を参照)、またはそれらに直接マップするもの (通常、最初の N 個のパラメーターを特定のプロセッサ レジスタのセットにマップすることによって) を使用するように定義されています。

これは可変引数リストでもよく出てきます。たとえば、printf次のように宣言されています。

int printf(const char* format, ...);

そして、その定義は通常、stdargシステムを使用して追加の引数を処理します。これは次のようになります。

#include <stdarg.h>

int printf(const char* format, ...)
{
    va_list ap;
    int result;
    va_start(ap, format);
    result = vprintf(format, ap);
    va_end(ap);
    return result;
}

標準の C 呼び出し規則を使用するプラットフォームを使用している場合、そのva_end(ap)マクロは通常、何も実行しません。この場合、余分な引数を関数に渡すことで回避できます。しかし、一部のプラットフォームではva_end()、スタックを予測可能な状態 (つまり、 への呼び出し前の場所) に復元するために呼び出しが必要ですva_start。そのような場合、関数は見つかった方法でスタックを離れることはありません (スタックから十分な引数をポップバックしません)。そのため、呼び出し元の関数は、たとえば、戻り値として偽の値をフェッチすると、終了時にクラッシュする可能性があります。住所。

于 2013-05-10T13:03:25.087 に答える
2

関数は必ずcdecl呼び出し規約 ( http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl ) を使用している必要があります。これにより、引数が右から左に逆の順序でスタックにプッシュされ、最後の引数を簡単に見つけ (スタックの一番上)、printfフォーマット文字列などの残りを解釈するために使用できるようになります。また、スタックをクリーンアップするのも呼び出し元の責任です。これは、関数自体がそうすることよりも少しコンパクトではありませんが (pascal/stdcall慣例のように)、可変引数リストを使用できることを保証し、末尾の引数を無視できることを意味します。 .

于 2013-05-10T12:54:19.177 に答える