2

Mitchellの本(プログラミング言語の概念)の第6.2.1章では、次のように述べています。

型キャスト。型キャストを使用すると、ある型の値を別の型として使用できます。特にCでは、整数を関数にキャストできるため、正しい形式の命令が含まれていない場所にジャンプしてC関数にすることができます。

だから私はこの非安全性を使うつもりで、何か変わったことをしました。私は次のようなことを試みました(擬似コード):

int x = 0;
print "loop";
x();

無限ループを作成します。変更してテストしてみましたが、うまくいきませんでした。どうすればこれらのもののような何かまたは他のすべてを行うことができますか?

前もって感謝します

4

4 に答える 4

6

それはそれがどのように機能するかではありません。

Cでは、整数値を関数ポインター値にキャストしてから、そのような関数ポインターを呼び出すことができます。

void (*ptr)() = (void (*)())42;
ptr();

ただし、これは、実行していることを正確に理解していない限り、おそらくクラッシュにつながる可能性があります。つまり、アドレス42で、そのシグネチャで関数を開始することを何らかの方法ですでに知っている場合です。この例のように固定アドレスを持つことは、システムプログラミングでは発生する可能性がありますが、アプリケーションプログラミングでは非常にまれです。

(特にWindowsプログラミングで)望ましいよりも実際に頻繁に発生するのは、いくつかの整数にキャストされた関数ポインター( LPARAM/WPARAM誰かのコールバック?)を渡し、それらを関数ポインターにキャストして実際に使用することです。

また、そのようなキャスト/呼び出しが実行されたときに何が起こるかは、C標準の範囲外であり、実装はこの点で自由に実行できます。

于 2012-12-30T14:57:18.877 に答える
3

@Soroush、舞台裏で何が起こっているのかをよりよく理解するのに役立つかもしれない例を次に示します。

#include <stdio.h>

int main(void)
{
    printf("begin\n");
    printf("loop\n");

    // declare a function pointer
    int (*loopPtr)();
    // set the function pointer to the current function
    loopPtr = main;
    // skip over the first printf();
    loopPtr += 22;
    // call the new location
    loopPtr();
}

私にとっては、でコンパイルするとx86_64で動作しclang -O0ます(これは無限再帰であり、各関数呼び出しがスタックスペースをかみ砕くため、スタックが使い果たされるまで動作します)。

オフセット22は、コンパイルしてから逆アセンブルしmain()、2番目のアドレスから開始のアドレスを減算することによって決定しましたprintf()

まず、私はそれをコンパイルしました:

clang -O0 test.c

次にそれを分解しました:

otool -tv a.out

...この出力を生成しました:

[...]
_main:
0000000100000ee0    pushq   %rbp
0000000100000ee1    movq    %rsp,%rbp
0000000100000ee4    subq    $0x20,%rsp
0000000100000ee8    leaq    0x00000073(%rip),%rdi
0000000100000eef    movb    $0x00,%al
0000000100000ef1    callq   0x100000f40
0000000100000ef6    leaq    0x0000006c(%rip),%rdi
0000000100000efd    movl    %eax,0xf4(%rbp)
0000000100000f00    movb    $0x00,%al
0000000100000f02    callq   0x100000f40
[...]

_main:関数のエントリポイントを示しますmain()。最初のアドレスは0x100000ee0です。最初のcallq命令はprintf()スキップしたい最初の呼び出しに対応しているので、その直後のアドレス0x100000ef6を選択しました。0x100000ef6から0x100000ee0を引いた値は小数点以下22桁です。

于 2012-12-31T15:11:21.903 に答える
2

まあ、x()そのアドレスで魔法のように関数を呼び出さないでください。彼は次のような意味だと思います。

typedef void (*functionPtr)();

int x;
//...
functionPtr foo = (functionPtr)x;
//or
functionPtr goo = (functionPtr)&x;
foo();
于 2012-12-30T14:57:40.433 に答える
1

あなたの本は誤解を招きます。標準で許可されているのはキャスト操作だけです。これは、値が適合する場合にのみ可能です。このような関数ポインタの実行は、ほとんどの場合、未定義の動作です。それを可能にするには、システムをよく知っている必要があります。したがって、引用した段落の2番目の部分は次のとおりです。

特にCでは、整数を関数にキャストできるため、正しい形式の命令が含まれていない場所にジャンプしてC関数にすることができます。

その形では真実ではありません。特に、すべてのまともな最新のシステムではデータを実行できません。実行可能コードが含まれていると見なすことができるように、ページに特別なフラグを設定する必要があります。

于 2012-12-30T15:08:18.137 に答える