C++逆向分析——模版

模版

假设有一个冒泡排序的函数:

void Sort(int* arr, int nLength)
{
int i, k;
for (i = 0; i < nLength; i++)
{
for (k = 0; k < nLength-1-i; k++)
{
if(arr[k] > arr[k+1]) {
int temp = arr[k];
arr[k] = arr[k+1];
arr[k+1] = temp;
}
}
}
}

但是这个冒泡排序的函数只能对int类型,如果我们想要使用char类型的时候就要重新写一个函数,这就违背了C++的初衷,重复造轮子了:

images/download/attachments/12714553/image2021-4-17_11-17-32.png

那么如何避免重复造轮子呢?C++中使用模板来解决这个问题。

函数模板的语法是这样的:

template<class 形参名, class 形参名, ...>
返回类型 函数名(参数列表) {
函数体;
}

用模板的方式来修改一下这个冒泡排序函数:

template<class T>
void Sort(T* arr, int nLength)
{
int i, k;
for (i = 0; i < nLength; i++)
{
for (k = 0; k < nLength-1-i; k++)
{
if(arr[k] > arr[k+1])
{
T temp = arr[k];
arr[k] = arr[k+1];
arr[k+1] = temp;
}
}
}
}

在当前这个函数中我们只有一个地方需要替换修改,所以在写模板关键词时候尖括号内的class形参只有一个,而我们只需要将需要替换的地方改成形参的名字即可。

那么模版其原理是什么,编译器做了什么工作呢?我们可以看一下如下代码的反汇编代码:

images/download/attachments/12714553/image2021-4-17_11-34-34.png

使用不同类型的数组传入冒泡排序函数,观察一下执行地址:

images/download/attachments/12714553/image2021-4-17_11-33-30.png

可以看见,两个函数的地址完全不一样,这就说明模板的本质就是编译器会在看见不同的传入类型时创建不同的函数

模板除了可以在函数中使用也可以在结构体(类)中使用模板,其格式如下所示:

template<class 形参名, class 形参名, ...>
class 类名 {
...;
}

如下代码,一个结构体,有两个成员函数,一个是比较返回最大的数,一个则是最小的数:

struct Base {
int a;
int b;
 
char x;
char y;
 
int Max() {
if (a>b) {
return a;
} else {
return b;
}
}
 
char Min() {
if (x<y) {
return x;
} else {
return y;
}
}
};

但这个结构体已经指定了成员变量的数据宽度int、char,而我们想要比较任意类型的话,可以使用模板改造下这段代码:

 

这个模板想使用的话,我们就需要告诉编译器模板中的T、M分别对应什么,所以如果直接使用Base b;则无法编译。

使用如下格式即可:

Base<int, char> b;

images/download/attachments/12714553/image2021-4-17_15-11-54.png

 

在我的vs2022里实验下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
 
 
template<class T, class M>
struct Base {
    T a;
    T b;
 
    M x;
    M y;
 
    T Max() {
        if (a > b) {
            return a;
        }
        else {
            return b;
        }
    }
 
    M Min() {
        if (x < y) {
            return x;
        }
        else {
            return y;
        }
    }
};
 
void main() {
    Base<int, float> base;
    base.a = 3;
    base.b = 4;
    base.x = 1.2f;
    base.y = 3.4f;
 
    int max = base.Max();
    float min = base.Min();
    printf("%d %f\n", max, min);
    return;
}

 看下反汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    Base<int, float> base;
    base.a = 3;
004C197F C7 45 E8 03 00 00 00 mov         dword ptr [base],3 
    base.b = 4;
004C1986 C7 45 EC 04 00 00 00 mov         dword ptr [ebp-14h],4 
    base.x = 1.2f;
004C198D F3 0F 10 05 38 7B 4C 00 movss       xmm0,dword ptr [__real@3f99999a (04C7B38h)] 
004C1995 F3 0F 11 45 F0       movss       dword ptr [ebp-10h],xmm0 
    base.y = 3.4f;
004C199A F3 0F 10 05 3C 7B 4C 00 movss       xmm0,dword ptr [__real@4059999a (04C7B3Ch)] 
004C19A2 F3 0F 11 45 F4       movss       dword ptr [ebp-0Ch],xmm0 
 
    int max = base.Max();
004C19A7 8D 4D E8             lea         ecx,[base] 
004C19AA E8 CE F6 FF FF       call        Base<int,float>::Max (04C107Dh) 
004C19AF 89 45 DC             mov         dword ptr [max],eax 
    float min = base.Min();
004C19B2 8D 4D E8             lea         ecx,[base] 
004C19B5 E8 AF F6 FF FF       call        Base<int,float>::Min (04C1069h) 
004C19BA D9 5D D0             fstp        dword ptr [min] 
    printf("%d %f\n", max, min);
004C19BD F3 0F 5A 45 D0       cvtss2sd    xmm0,dword ptr [min] 
004C19C2 83 EC 08             sub         esp,8 
004C19C5 F2 0F 11 04 24       movsd       mmword ptr [esp],xmm0 
004C19CA 8B 45 DC             mov         eax,dword ptr [max] 
004C19CD 50                   push        eax 
004C19CE 68 30 7B 4C 00       push        offset string "%d %f\n" (04C7B30h) 
004C19D3 E8 FF F6 FF FF       call        _printf (04C10D7h) 

 

分析下:

 

看下1.2的16进制表示:

 

 

可以看到就是1.2和3.4,只是编译器,将这两个数字放到了常量区。

 

因此,本质上,模板就是给不同的类和函数在编译期间根据类型取了一个新的名字

 

 

 

 

 

posted @   bonelee  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
历史上的今天:
2020-04-09 OWASP TOP10 Web应用十大安全风险_2017
2020-04-09 移动互联网恶意程序描述格式 ——移动端恶意软件分类再合适不过了
2020-04-09 用短信传播病毒:最新Android手机勒索软件Koler
2020-04-09 2019年Android恶意软件专题报告:未来移动安全呈现四大趋势——资费消耗与隐私窃取分别以高达46.8%和41.9%的占比,成为横行无忌的主要恶意软件类型,其次分别为远程控制、流氓行为、恶意扣费和欺诈软件。
2020-04-09 2018年Android恶意软件专题报告
2020-04-09 浅析Android恶意应用及其检测技术
2020-04-09 Android恶意软件特征及分类
点击右上角即可分享
微信分享提示