【C++逆向 - 1】C++函数新特性

内联函数

本质:用函数代码替换函数调用

使用方式:在函数声明和函数定义前加上 inline 关键字

笔者感觉跟C语言中的宏定义差不多,但是内联函数更加“智能”(应该是编译器更加智能)。即使程序员将函数作为内联函数,但是编译器会检查是否满足一些要求,比如是否是递归调用,函数是否过大等。

笔者还是喜欢宏,当然因人而异

引用变量

int a;
int &b = a;
int c = 20;
b = c; // ==> 这里是将 c 的值赋给 b 即 b = a = 20 [其实b的值是a的地址......]

注意:引用变量必须初始化;引用变量不得更换引用对象

本质:b 保存着 a 的地址;每次操作 b 时,会取出其保存的 a 的地址进行操作【是不是感觉跟指针很像,但是指针操作每次还得解引用,所以这里可以看作<指针+自解引用>】

考虑如下代码:

#include 
#include 
#include 
using namespace std;

int add(int a, int b) { return a + b; }
inline int sub(int a, int b) { return a - b; }

int main()
{

        int a = 10;
        int &b = a;
        b = 20;
        cout << &a << " " << &b << endl;
        cout << a << " " << b << endl;
        return 0;
}

输出:

0x7ffffa5b781c 0x7ffffa5b781c
20 20

IDA 里面直接识别成的指针:

【C++逆向 - 1】C++函数新特性_第1张图片

这里看网上很多人说变量a和变量b的地址是相同的,其实这里我感觉是错误的,变量a和变量b的地址并不相同,因为调试发现变量b保存的是变量a的地址,但是在进行相关操作时会进行特殊处理,比如:

 &b    ==> 其实取的是b保存的a的地址

 b=10 ==> 其实是将变量b保存的a的地址pa取出来,然后执行 [pa] = 10

当然这里说了引用变量保存的是引用对象的地址,跟指针差不多,所以 int &b = a + 3; 是错误的

但好像早期的编译器是允许将表达式作为引用对象的,但是都2023年了,你懂的,其实笔者在函数传参啥的时候还是更喜欢直接用指针,但是后面类的设计,引用就是必不可少的了

当然也要注意当引用作为返回值时,别把局部变量引用返回了,比如如下代码:

#include 
#include 
#include 
using namespace std;

int& func(int &a)
{
        int b = a;
        return b;
}

int main()
{
        int a = 10;
        int &b = func(a);
        cout << &a << " "  << &b << endl;
        b = 100;
        return 0;
}

输出如下:

0x7ffc6e6b2f7c 0
Segmentation fault (core dumped)

默认参数

跟 python 的差不多,就没啥好说的了,注意点:

1)如果 i 位置为默认参数,则 i+x 位置应该都是默认参数

2)如果第 i 个默认参数被赋值,则第 i+x 个默认参数应当都被赋值

函数重载

函数重载条件:

        1)具有不同的函数特征标 ==> 人话就是参数列表不同

        2)函数调用不具有二义性(或多义性)==> 最佳匹配情况下

其实关键的地方在于函数调用的二义性:考虑如下代码

include 
#include 
using namespace std;

int func(int a, int b, int c = 10)
{
        return a + b + c;
}

int func(int a, int b)
{
        return a + b;
}

int main()
{
        int a = func(20, 30, 10);
        int b = func(20, 30);
        return 0;
}

这里的函数特征标确实不同,但是在调用函数时具有二义性,即 func(20, 30); 两个函数都可以匹配。如果把 int b = func(20,30); 去掉则可以成功编译

这里我把代码稍微改一下下:仅仅将第二个 func 的第二个参数类型改为 long long

#include 
#include 
#include 
using namespace std;

int func(int a, int b, int c = 10)
{
        cout << "func 3 argc" << endl;
        return a + b + c;
}

int func(int a, long long b)
{
        return a + b;
}

int main()
{

        int a = func(20, 30, 10);
        int b = func(20, 30);
        return 0;
}

这个时候又可以成功编译,为啥这时不具备二义性呢?这里的二义性是在最匹配的情况下,因为对于 int b = func(20, 30); 来说,30 为 int 类型(在 int 范围内的下常数为 int 类型),所以这里会匹配第一个 func 函数:输出如下

func 3 argc
func 3 argc

函数模板

用法:template Function

本质:就是将函数展开

给个demo:

ing namespace std;

// typename 可以用 class 关键字代替
template 
void Swap(T &a, T &b)
{
        T temp = a;
        a = b;
        b = temp;
}

int main()
{
        int a = 10, b = 20;
        float fa = 20.0, fb = 30.0;
        Swap(a, b);
        Swap(fa, fb);
        cout << a << " " << b << endl;
        cout << fa << " " << fb << endl;
        return 0;
}

IDA 中识别如下:

【C++逆向 - 1】C++函数新特性_第2张图片

 但是 Swap 还是用的 int(但是问题不大,都是32位的数据类型):

【C++逆向 - 1】C++函数新特性_第3张图片

显式具体化

上面的模板实现存在一定问题,比如如果T为数组呢?所以可以具体化一个模板:

#include 
#include 
#include 
using namespace std;

// typename 可以用 class 关键字代替
template 
void Swap(T &a, T &b)
{
        T temp = a;
        a = b;
        b = temp;
        cout << "Swap 0" << endl;
}

// 具体化
template <> void Swap(float &a, float &b)
{
        cout << "Swap 1" << endl;
}

int main()
{
        int a = 10, b = 20;
        float fa = 20.0, fb = 30.0;
        Swap(a, b);
        Swap(fa, fb);
        return 0;
}

输出如下:

Swap 0
Swap 1

如果把函数重载考虑进来,调用关系如下:

        非模板函数>具体化函数>模板化函数 

decltype 关键字

decltype 关键字主要是争对模板类型的,考虑如下代码:

#include 
#include 
#include 
using namespace std;

// typename 可以用 class 关键字代替
template 
auto add(T1 a, T2 b) -> decltype(a + b)
{
        decltype(a + b) sum = a + b;
        return sum;
}

int main()
{
        int a = 10, b = 20;
        float fa = 20.0, fb = 30.0;
        int c = add(a, fb);
        float fc = add(fa, b);
        cout << c << " " << fc << endl;
        return 0;
}

这里的问题就是由于 T1 和 T2 的类型我们事先都不知道,所以这里的 sum 和返回类型是啥呢?这里显然也是不知道的,为了解决这里问题,引入了 decltype 关键字

decltype (expression) var;

        1)若 expression 是没用括号的标识符,则 var 类型与该标识符相同

        2)若 expression 是一个函数调用,则 var 类型为函数返回值类型

        3)若 expression 是用括号的左值,则 var 类型为引用类型

        4)若以上都不满足,则 var 类型与 expression 相同

        int a = 10;
        decltype (a) b;
        decltype ((a)) c = b;

注意这里的 c 为引用类型,所以记得初始化。 

总结

这里仅仅回顾了一些 C++ 的最基本的知识,并没用深入,注意是为之后的逆向做准备。不熟悉或没接触过 C++ 的读者建议好好的去深入学习下上面所讲的知识,最好自己动手写写代码。

你可能感兴趣的:(C++逆向,c++)