6

MSDN のドキュメントによると、クラス関数の既定の__thiscall呼び出し規約を使用すると、"this" ポインターが ECX に格納されます。これは、通常の C++ コードを変換する場合には確かに当てはまりますが、インライン アセンブリで「this」にアクセスしようとしたときに問題が発生しました。

テストプログラムは次のとおりです。

#include <cstdio>

class TestClass
{
    long x;

    public:
        inline TestClass(long x):x(x){}

    public:
        inline long getX1(){return x;}
        inline long getX2()
        {
            _asm
            {
                mov eax,dword ptr[ecx]
            }
        }
};
int main()
{
    TestClass c(42);

    printf("c.getX1() = %d\n",c.getX1());
    printf("c.getX2() = %d\n",c.getX2());

    return 0;
}

2 つの Get 関数は次のように変換されます。

?getX1@TestClass@@QAEJXZ (public: long __thiscall TestClass::getX1(void)):
  00000000: 8B 01              mov         eax,dword ptr [ecx]
  00000002: C3                 ret

?getX2@TestClass@@QAEJXZ (public: long __thiscall TestClass::getX2(void)):
  00000000: 8B 01              mov         eax,dword ptr [ecx]
  00000002: C3                 ret

これら 2 つの機能は同一であると言っても過言ではありません。それでも、プログラムからの出力は次のとおりです。

c.getX1() = 42
c.getX2() = 1

明らかに、「これ」は2 番目の Get 関数が呼び出されたときに ECX に格納されないため、私の質問は次のとおりです。インライン アセンブリを含むクラス関数が呼び出し規則に従っていること、および/または通常/非インラインと同じ方法で呼び出されていることを確認するにはどうすればよいですか?機能?

編集: main 関数は次のように変換されます。

_main:
  00000000: 51                 push        ecx
  00000001: 6A 2A              push        2Ah
  00000003: 68 00 00 00 00     push        offset $SG3948
  00000008: E8 00 00 00 00     call        _printf
  0000000D: 83 C4 08           add         esp,8
  00000010: 8D 0C 24           lea         ecx,[esp]
  00000013: E8 00 00 00 00     call        ?getX2@TestClass@@QAEJXZ
  00000018: 50                 push        eax
  00000019: 68 00 00 00 00     push        offset $SG3949
  0000001E: E8 00 00 00 00     call        _printf
  00000023: 33 C0              xor         eax,eax
  00000025: 83 C4 0C           add         esp,0Ch
  00000028: C3                 ret
4

5 に答える 5

8

ドキュメントを読み違えているのか、それとも書き方が悪いのかはわかりませ__thiscallんが、 ポインターがECXに格納されているという意味ではありません。これは、オブジェクトへのポインターが ECXで渡されることを意味します。より大きな関数では、関数内の異なる場所であるレジスタから別のレジスタに移動するのを見てきました。場合によっては、メモリにスピルするのを見てきました。それが ECX にあるとは期待できません。また、関数内の他のコードやコンパイラに渡される最適化フラグに応じて、その場所が変わる可能性があります。this

あなたの場合、関数がインラインであり、おそらくインライン化されているという事実により、問題はさらに複雑になります。(ただし、 _asmインライン化を阻害する可能性があります。) 定数の伝播 (非常に単純で広く使用されている最適化手法) は、ほぼ確実に、 への呼び出しがc.getX1()を使用するだけ42であり、関数呼び出しも何へのアクセスもありませんc

一般に、インライン アセンブラはトリッキーな問題です。コンパイラがどのレジスタを何に使用しているかがわからないからです。通常、実際のアセンブラ命令に加えて、使用するレジスタや変数などをコンパイラに伝えるディレクティブがあり、アセンブラで変数自体を参照できるようになります。これらを使用しない限り、インライン アセンブラに関してはほとんど想定できません。

ただし、各コンパイラには独自のルールがあります。多くの場合、特別な構文を使用します。mov eax, [cx].xたとえば、 またはのようなmov eax, xものは、Microsoft インライン アセンブラーが必要とするものかもしれません。いずれにせよ、あなたが書いたものから、コンパイラーがあなたがアクセスしていると推測できる可能性はありませんc.x. そして、他のすべての使用は定数の伝播によって排除されているため、変数を生成することさえある非常に貧弱なコンパイラになりますc

編集:

FWIW: Microsoft のインライン アセンブラーのドキュメントは http://msdn.microsoft.com/en-us/library/4ks26t93%28v=vs.71%29.aspxにあります。詳しくは見ていませんが、「__asm ブロックでの C または C++ シンボルの使用」に関するセクションがあります。xこれはおそらく、コンパイラがアクセスされたことを知る方法で、インラインアセンブラでアクセスする方法を説明しxます。

于 2012-08-30T10:21:59.160 に答える
4

問題は、私が MS コンパイラについて見たことから、コンパイラは が[ecx]と同じであることを認識しthis->xていないため、コンパイラはメンバー変数がアクセスされていることを認識していないことです (関数を介したデータ フローのトレース)。トリッキーです)。

getX1コンパイラは、オブジェクトのコンストラクターへの呼び出しを最適化し、コンストラクターに渡される定数でインライン化しました。これは、呼び出しgetX2が行われたときにオブジェクトが正しく構築されていないことを意味します。これは、コンパイラの観点から、関数getX2はどのメンバーにもアクセスしないため、正しく構築する必要がないためです。MS コンパイラでは、メンバー変数が使用されていることをコンパイラに伝える方法は見たことがありません[ecx]TestClass.x

そして、何度も言及されているinlineことは、コンパイラによって無視されることがよくあります. この場合、_asmブロックを持つ関数はインライン化されず、他の関数はインライン化/書き換えられます。

于 2012-08-30T10:31:33.603 に答える
2

thisは、実際には 内に格納されますecx。少なくとも、オブジェクトが最適化されていない場合にオブジェクトが存在していたであろうアドレス:

00000010: 8D 0C 24           lea         ecx,[esp]

問題は、オプティマイザがアセンブリ コードを実際には理解していないことです。そのため、コードの正しさの責任はユーザーにあります。次のように、オブジェクトを呼び出しにインライン42化できることがわかっているため、オブジェクトを削除するだけです。printf

printf("c.getX1() = %d\n",42);

機能させるには、次のように定義getX2noinlineます。

long __declspec(noinline) getX2() { ... }

これにより、オプティマイザーはそれを完全なブラックボックスと見なすようになるため、cオブジェクトにアクセスするかどうかに関して何も仮定しません。これは確かに私にとってはうまくいきますが、文書化されていません。

代わりに、MSVC でインライン アセンブリを使用しないことをお勧めします。インライン アセンブリは 64 ビット コンパイルでもサポートされていません。代わりに MASM を使用してください。これにより、将来のフラストレーションも解消されます。

于 2012-08-30T10:04:32.030 に答える
1

関数をインライン化したためecx、特に関数の残りの部分ではオブジェクト プロパティやメソッドを使用しないため、コンパイラは asm コードを呼び出す前に正しく設定する必要があることを知りません。

最初以外のメソッドを宣言および定義してみてくださいinlinegetX2非インラインの非メンバー関数を別の翻訳単位で定義する方がよい場合があるため、コンパイラは最適化の機会を制限されます。

于 2012-08-30T09:50:12.920 に答える
1

これは、関数を正しく機能させる方法です(つまり、ECXで「this」を渡します):

testclass.hpp :

class TestClass
{
    long x;

    public:
        inline TestClass(long x):x(x){}

    public:
        long getX1();
        long getX2();
};

testclass.cpp :

#include "testclass.hpp"

long TestClass::getX1()
{
    return x;
}
long TestClass::getX2()
{
    _asm
    {
        mov eax,dword ptr[ecx]
    }
}

testmain.cpp :

#include <cstdio>
#include "testclass.hpp"

int main()
{
    TestClass c(42);

    printf("c.getX1() = %d\n",c.getX1());
    printf("c.getX2() = %d\n",c.getX2());

    return 0;
}

出力:

c.getX1() = 42
c.getX2() = 42

問題は、MSVC 2010 のインライン化されたクラス関数が、MSDN で指定された呼び出し規則に必ずしも従わないことです。これはバグではないと思いますが、インライン関数でインライン アセンブリを使用する予定がある場合は、少なくとも注意する必要があります。私のアドバイスは、あなたがそれをしないことです。クラス関数でインライン アセンブリが必要な場合は、宣言と実装を分けてください。

于 2012-08-30T11:05:22.370 に答える