Nisy 发表于 2010-1-16 17:38:43

类中未调用的成员方法会被编译进EXE吗?


编译器的解决方案
应该会先将类编译为 obj 文件,然后将这些obj进行逐个拼接来生成最终的EXE
拼接过程会将类中所有成员方法全部COPY到EXE进行封装吗
还是在最终的拼接中只提取需要的成员方法 根据其偏移地址来填补调用函数的地址或其他

//////////////////////////////////////////////////////////////////////////
// 类方法声明
//////////////////////////////////////////////////////////////////////////
class Test
{
public:
        Test();
        virtual ~Test();
public:
        int m_a;
        int m_b;
public:
        int Add();
        void Set(int a,int b);
};

//////////////////////////////////////////////////////////////////////////
// 类方法实现
//////////////////////////////////////////////////////////////////////////
int Test::Add()
{
        return m_a + m_b;
}       

void Test::Set(int a,int b)
{
        m_a = a;
        m_b = b;
}
//////////////////////////////////////////////////////////////////////////
// main函数调用
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
        Test tt;
        tt.Set(10,20);
        printf("%d %d",tt.m_a,tt.m_b);
        return 0;
}
//////////////////////////////////////////////////////////////////////////
// main函数结束
//////////////////////////////////////////////////////////////////////////

在Debug编译模式中可见:

@ILT+0(??1Test@@UAE@XZ):
00401005   jmp         Test::~Test (004011c0)
@ILT+5(?Add@Test@@QAEHXZ):
0040100A   jmp         Test::Add (00401200)          // 在Debug模式下确实是有Add这个未使用的方法的
@ILT+10(??_GTest@@UAEPAXI@Z):
0040100F   jmp         Test::`scalar deleting destructor' (00401150)
@ILT+15(??_GTest@@UAEPAXI@Z):
00401014   jmp         Test::`scalar deleting destructor' (00401150)
@ILT+20(?Set@Test@@QAEXHH@Z):
00401019   jmp         Test::Set (00401240)
@ILT+25(_main):
0040101E   jmp         main (00401050)
@ILT+30(??0Test@@QAE@XZ):
00401023   jmp         Test::Test (00401110)

注意哦 先调用一下该方法 编译后EXE中一定会有该代码 想要测试的话 需要先Clear之后再进行搜索哦 就会发现VC已经不搭理他了


那 Release 版会是什么情况呢? 如果说Debug版是为了我们调试才全盘封装的,Release版是否会将这些无用代码阉割呢 ?
那有如何来得出最终结论呢?得出这个结论又有什么作用呢?

这里提供两种思路:
一种是在未调用方法中 输入一个特殊字符串,我们根据生成的文件中是否有该字符串来判定;
另一种是先调用下该函数(如Add),调用后用OD等工具动态跟踪出该函数编译后的源码,将其特征码提取

004010C0/$8B41 08       MOV EAX,DWORD PTR DS:             ;m_b
004010C3|.8B51 04       MOV EDX,DWORD PTR DS:             ;m_a
004010C6|.03C2          ADD EAX,EDX                              ;m_a + m_b
004010C8\.C3            RETN                                     ;return eax

特征码为:8B 41 08 8B 51 04 03 C2 C3

然后用工具进行内存搜索 若有便可得出真的编译进去了 ~

Nisy 发表于 2010-1-16 18:02:38

//////////////////////////////////////////////////////////////////////////
// main 函数样本
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
        Test tt;
        tt.Set(10,20);
        tt.Add();
        printf("%d %d",tt.m_a,tt.m_b);
        return 0;
}
//////////////////////////////////////////////////////////////////////////
// main 函数结束
//////////////////////////////////////////////////////////////////////////

00401000/$6A FF         PUSH -1
00401002|.68 A8684000   PUSH test.004068A8                     ;SE handler installation
00401007|.64:A1 0000000>MOV EAX,DWORD PTR FS:               ;开始处理异常
0040100D|.50            PUSH EAX
0040100E|.64:8925 00000>MOV DWORD PTR FS:,ESP
00401015|.83EC 0C       SUB ESP,0C                           ;把ESP向上移三格
00401018|.8D4C24 00   LEA ECX,DWORD PTR SS:
0040101C|.E8 5F000000   CALL test.00401080                     ;构造
00401021|.6A 14         PUSH 14
00401023|.6A 0A         PUSH 0A
00401025|.8D4C24 08   LEA ECX,DWORD PTR SS:         ;以为压两个参数 这里 ESP+8 为this指针
00401029|.C74424 1C 000>MOV DWORD PTR SS:,0            ;给 0 做什么
00401031|.E8 9A000000   CALL test.004010D0                     ;tt.Set()
00401036|.8D4C24 00   LEA ECX,DWORD PTR SS:             ;这里ECX位置是虚表
0040103A|.E8 81000000   CALL test.004010C0                     ;tt.Add
0040103F|.8B4424 08   MOV EAX,DWORD PTR SS:
00401043|.8B4C24 04   MOV ECX,DWORD PTR SS:
00401047|.50            PUSH EAX
00401048|.51            PUSH ECX
00401049|.68 40804000   PUSH test.00408040                     ;ASCII "%d %d"
0040104E|.E8 9D000000   CALL test.004010F0
00401053|.83C4 0C       ADD ESP,0C                           ;自动平衡堆栈
00401056|.8D4C24 00   LEA ECX,DWORD PTR SS:
0040105A|.C74424 14 FFF>MOV DWORD PTR SS:,-1         ;ESP+14 给 -1 做什么
00401062|.E8 49000000   CALL test.004010B0
00401067|.8B4C24 0C   MOV ECX,DWORD PTR SS:         ;??
0040106B|.33C0          XOR EAX,EAX
0040106D|.64:890D 00000>MOV DWORD PTR FS:,ECX               ;??
00401074|.83C4 18       ADD ESP,18
00401077\.C3            RETN


--------------------------------------------------

构造的时候 往虚表中传入了一个地址
构造函数:
00401080/$8BC1          MOV EAX,ECX
00401082|.C700 C0704000 MOV DWORD PTR DS:,test.004070C0// 其中0X004070C0 中存放 0X0401090
00401088\.C3            RETN

构造和析构都给一个奇怪的函数指针:

00401090   .56            PUSH ESI
00401091   .8BF1          MOV ESI,ECX
00401093   .E8 18000000   CALL test.004010B0
00401098   .F64424 08 01TEST BYTE PTR SS:,1
0040109D   .74 09         JE SHORT test.004010A8
0040109F   .56            PUSH ESI
004010A0   .E8 51040000   CALL test.004014F6
004010A5   .83C4 04       ADD ESP,4
004010A8   >8BC6          MOV EAX,ESI
004010AA   .5E            POP ESI
004010AB   .C2 0400       RETN 4
004010AE      90            NOP
004010AF      90            NOP
004010B0/$C701 C0704000 MOV DWORD PTR DS:,test.004070C0
004010B6\.C3            RETN

析构函数:
004010B0/$C701 C0704000 MOV DWORD PTR DS:,test.004070C0
004010B6\.C3            RETN


这里一些代码还是没看明白 尤其是构造和析构时 给该对象虚表中一个特殊的函数指针
还有就是 main入口和结束时对异常链的处理 不太懂为什么是这些代码

/////////////////////////////////////////////////////////
//看一下入口的这点东西
/////////////////////////////////////////////////////////

00401000/$6A FF         PUSH -1
00401002|.68 A8684000   PUSH test.004068A8                     ;SE handler installation
00401007|.64:A1 0000000>MOV EAX,DWORD PTR FS:               ;开始处理异常
0040100D|.50            PUSH EAX
0040100E|.64:8925 00000>MOV DWORD PTR FS:,ESP
00401015|.83EC 0C       SUB ESP,0C                           ;把ESP向上移三格
00401018|.8D4C24 00   LEA ECX,DWORD PTR SS:             ;ESP 已经到 -18 了
0040101C|.E8 5F000000   CALL test.00401080                     ;构造

到构造时 ESP 已经退到 -18了

$-18   > 0000D9D2
$-14   >/0012FFC0
$-10   >|00403C44RETURN to test.00403C44 from kernel32.SetUnhandledExceptionFilter
$-C      >|0012FFB0Pointer to next SEH record
$-8      >|004068A8SE handler
$-4      >|FFFFFFFF
$ ==>    >|004015B5RETURN to test.<ModuleEntryPoint>+0B4 from test.00401000

--------------------------------------
Set() 函数
--------------------------------------

00401021|.6A 14         PUSH 14
00401023|.6A 0A         PUSH 0A
00401025|.8D4C24 08   LEA ECX,DWORD PTR SS:         ;因为压两个参数 这里 ESP+8 为this指针
00401029|.C74424 1C 000>MOV DWORD PTR SS:,0            ;给 0 做什么 // 这里的是是压入 main 的第一个参数
00401031|.E8 9A000000   CALL test.004010D0                     ;tt.Set()

004010D0/$8B4424 04   MOV EAX,DWORD PTR SS:
004010D4|.8B5424 08   MOV EDX,DWORD PTR SS:
004010D8|.8941 04       MOV DWORD PTR DS:,EAX
004010DB|.8951 08       MOV DWORD PTR DS:,EDX
004010DE\.C2 0800       RETN 8

$-C      > 00401036RETURN to test.00401036 from test.004010D0
$-8      > 0000000A
$-4      > 00000014
$ ==>    > 004070C0test.004070C0
$+4      > 0000000A; m_a
$+8      > 00000014; m_b

看一下析构之后的代码

00401060|.8B4C24 10   MOV ECX,DWORD PTR SS:            ;NEXT SEH record
00401064|.33C0          XOR EAX,EAX
00401066|.64:890D 00000>MOV DWORD PTR FS:,ECX               ;出函数时还原异常链
0040106D|.83C4 1C       ADD ESP,1C                               ;??
00401070\.C3            RETN

Nisy 发表于 2010-1-16 18:25:11

哈哈 原来这个东西就是函数内 所有变量的总空间大小 ~~

SUB ESP,0C

Debug 版要测定局部变量空间 需-44H

11:   int main(int argc, char* argv[])
12:   {
00401050   push      ebp
00401051   mov         ebp,esp
00401053   push      0FFh
00401055   push      offset __ehhandler$_main (00413419)
0040105A   mov         eax,fs:
00401060   push      eax
00401061   mov         dword ptr fs:,esp
00401068   sub         esp,50h            // here 真是的 大小应该为 50-44=0c
0040106B   push      ebx
0040106C   push      esi
0040106D   push      edi
0040106E   lea         edi,
00401071   mov         ecx,14h
00401076   mov         eax,0CCCCCCCCh
0040107B   rep stos    dword ptr


// ps : 在Debug模式下 为调用的函数也是不会被编译的 :loveliness:
页: [1]
查看完整版本: 类中未调用的成员方法会被编译进EXE吗?