なぜvtableは同じ関数を複数回参照しますか?


1

私はIDA 7でVC++アプリケーションを逆コンパイルしていますが、ここで "Object__pure"と呼ばれるもののように同じ関数を複数回参照するvftablesを見つけることが多く、他のほとんどすべてのクラスによって継承された "重いベース"の部分Objectアプリケーション:Objectから継承私の実行可能で

seg002:008F4748  const Object::'vftable' dd offset Object__free ; DATA XREF: Object__ctor+12↑o Object__dtor+A↑o Object__copy+16↑o 
seg002:008F474C   dd offset Object__vsub_7D0990 
seg002:008F4750   dd offset Object__vsub_7D09A0 
seg002:008F4754   dd offset Object__pure 
seg002:008F4758   dd offset Object__pure 
seg002:008F475C   dd offset Object__pure 
seg002:008F4760   dd offset Object__pure 
seg002:008F4764   dd offset Object__vsub_47B660 

子クラスは、一般的に、独自のカスタム関数の代わりに、彼らのvtableの中にそれらの「純粋な」(私はそれらを呼ばれる方法)のものを持っています。

私はそれが仮想であるか純粋な仮想呼び出しのようにそれについて考えて「純粋な」名前を付けました。そのような機能は、複数回参照されるのはなぜ

seg000:007E4580  Object__pure proc near ; CODE XREF: <lots!> 
seg000:007E4580 
seg000:007E4580  a1 = dword ptr -4 
seg000:007E4580  arg_0= dword ptr 8 
seg000:007E4580 
seg000:007E4580 000  push ebp 
seg000:007E4581 004  mov  ebp, esp 
seg000:007E4583 004  push ecx 
seg000:007E4584 008  mov  [ebp+a1], ecx 
seg000:007E4587 008  mov  eax, [ebp+arg_0] 
seg000:007E458A 008  push eax 
seg000:007E458B 00C  mov  ecx, [ebp+a1] ; this 
seg000:007E458E 00C  call Object__vsub_80CD50 ; Call Procedure 
seg000:007E4593 00C  mov  esp, ebp 
seg000:007E4595 004  pop  ebp 
seg000:007E4596 000  retn 4 ; Return Near from Procedure 
seg000:007E4596  Object__pure endp 

... 

seg000:0080CD50  Object__vsub_80CD50 proc near ; CODE XREF: <lots again!> 
seg000:0080CD50 
seg000:0080CD50  var_4= dword ptr -4 
seg000:0080CD50 
seg000:0080CD50 000  push ebp 
seg000:0080CD51 004  mov  ebp, esp 
seg000:0080CD53 004  push ecx 
seg000:0080CD54 008  mov  [ebp+var_4], ecx 
seg000:0080CD57 008  mov  esp, ebp 
seg000:0080CD59 004  pop  ebp 
seg000:0080CD5A 000  retn 4 ; Return Near from Procedure 
seg000:0080CD5A  Object__vsub_80CD50 endp 

:関数自体は完全に空Object__vsub_80CD50を呼び出す以外に何もしませんか?それは最適化、何もしない機能の統合によるものですか?これらの機能は、通常仮想/純粋仮想ですか?

+2

これらの2つの機能では、「何もしません」ということはありません。スタックで何もしない場合でも、スタックから整数を消費します。したがって、デバッグヘルパーの残骸かもしれません。より典型的な "空の"ルーチンはちょうど 'retf'でしょう。 「純粋な」仮想関数が警告を出力することがあります。 25 12月. 172017-12-25 20:39:56

  0

情報をいただきありがとうございます。デバッグレムナントに関するメモは興味深いものです。何もしないメソッドについては、最後のメソッド( 'vsub_80CD50')は' __thiscall'(それがそうであるように)のように解釈されても何も効果的でなくても、 'this'ポインタをecxに押し込んだり、すぐに? 25 12月. 172017-12-25 22:21:19

  0

2番目のルーチンは 'retn * immediate *'で終わるので、スタックからも値を削除します。あなたがどこから呼び出されたかを調べると、 'ecx'(そしておそらく他のレジスタ)を初期化することを除いて、余分な引数がプッシュされることがわかります。 26 12月. 172017-12-26 10:16:36

+4

特定の条件下で、コンパイラは、コンパイル時に同じオブジェクトコードを使用するために、同じ実装で異なるソースコード関数を最適化することがあります。 26 12月. 172017-12-26 10:56:48

1

私が推測してコメントで確認されたように、これは明らかに同じ論理を実行するいくつかのコンパイラ最適化再利用方法です。私はここで、データストリームのリーダ/ライタのように、より具体的なオブジェクトの一つの方法を逆転させたとき

これは私には明らかだ:

seg002:008F4FC4  const DataStream::'vftable' dd offset DataStream__readByte ; DATA XREF: DataStream__ctor+12↑o 
seg002:008F4FC8   dd offset DataStream__readWord 
seg002:008F4FCC   dd offset DataStream__readDword 
seg002:008F4FD0   dd offset DataStream__readBytes 
seg002:008F4FD4   dd offset DataStream__canReadWrite 
seg002:008F4FD8   dd offset DataStream__writeByte 
seg002:008F4FDC   dd offset DataStream__writeWord 
seg002:008F4FE0   dd offset DataStream__writeDword 
seg002:008F4FE4   dd offset DataStream__writeBytes 
seg002:008F4FE8   dd offset DataStream__canReadWrite 

あなたが見ることができる方法canReadcanWriteは単純に最適化しました

bool __thiscall DataStream::canReadWrite(DataStream *this, int lengthRequired) 
{ 
    return this->members.pData <= this->members.pDataEnd 
     && this->members.pDataEnd - this->members.pData >= lengthRequired; 
} 

それはいくつかの他の場合であってもよい。一つの方法の両方のためのロジックが同じ(hexrays出力)であるように(私はcanReadWriteと命名する)にデータストリームクラス(読み込み専用または書込み専用)は、各メソッドを別々に実装します(この場合、単にfalseを返します)。

このように、上記のObjectのようなさらに一般的な基本クラスでは、多くの方法が特に特化せず、1つに最適化されています。