【C】C语言内存操作函数的解析

0. 前言

        当我们有一个字符char str1[] = "hello world!",此时,我们要新建一个字符串str2,想让str2的内容和str1一模一样,可以使用字符串函数strcpy,对两个字符串进行比较,可以使用strcmp,对字符串的操作,C语言内置的库已经给我们把解决方案都罗列好了。

        但是,如果我们已经有了一个数组int arr1[],新建一个数组arr2,让arr2的内容和arr1完全一样呢,这个时候又该怎么办呢?

// 怎么让cp_arr的内容和arr完全一样?
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int cp_arr[10] = { 0 };

  我们是否要这样做:

void PrintArr(int* arr, size_t num)
{
	for (size_t i; i < num; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int cp_arr[20] = { 0 };

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
	{
		cp_arr[i] = arr[i];
	}
	printf("arr: ");
	PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
	
	printf("cp_arr: ");
	PrintArr(arr, sizeof(cp_arr) / sizeof(cp_arr[0]));

	return 0;
}

这种方法可行,但未免也太过麻烦。

        C语言在标准库里早已经为我们实现好了处理这类情况的解决方案,他们都是对内存块进行操作,这类接口都是以mem开头,称为内存操作函数。

最核心最标准的内存操作函数有如下几种:

  • memcpy内存拷贝(不处理重叠内存,效率高)
  • memmove内存移动(处理重叠内存,更安全)
  • memset内存填充(批量设置字节,常用初始化 / 清零)
  • memcmp内存比较(按字节对比两块内存)
  • memchr内存查找(在内存块中搜索指定字节)

而memcpy就是专门解决类似刚刚那种内存拷贝的问题:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int cp_arr[10] = { 0 };

/*for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
	cp_arr[i] = arr[i];
}*/

memcpy(cp_arr, arr, sizeof(arr));

printf("arr: ");
PrintArr(arr, sizeof(arr) / sizeof(arr[0]));

printf("cp_arr: ");
PrintArr(cp_arr, sizeof(cp_arr) / sizeof(cp_arr[0]));

return 0;

运行结果:

下面,我们来对这些内存操作函数进行详细了解。

1. 内存操作函数

        mem开头的这些内存操作函数需要包含统一的头文件<string.h>(C++中是<cstring>),虽然他们是内存操作函数,但还是把他们归到了<string.h>中。

        内存操作函数的单位一定是字节,这一点要牢记,你指定多少字节,就给你操作多少字节

1.1 memcpy

1.1.1 函数功能介绍与使用

功能:source 指向的内存块,直接复制 num 个字节到 destination 指向的内存块。

参数介绍:

参数含义
destination目标内存地址(可被转换为void*类型的指针)
source源数据内存地址(可被转换为const void*类型的指针)
num要复制的字节数,类型为无符号整数size_t

使用:

先定义一个测试用的结构体:

// 定义一个测试用结构体
struct Student {
    int id;
    int age;
    char name[20];
};

可以拷贝int数组:

// ==========================
// 1. memcpy 拷贝 int 数组
// ==========================
int arr1[5] = { 10, 20, 30, 40, 50 };
int arr2[5] = { 0 }; // 初始化为0

// 拷贝 5个int 的内存
memcpy(arr2, arr1, 5 * sizeof(int));

printf("===== int数组拷贝 =====\n");
for (int i = 0; i < 5; i++) {
    printf("%d ", arr2[i]);
}
printf("\n\n");

        也可以拷贝char数组,拷贝char数组要注意一个问题,他一定是按照你指定的字节数去拷贝的,不会给你加上\0。

// 3. memcpy 拷贝 char数组
// 【重点:不处理 \0,只按字节拷贝】
// ==========================
char str1[20] = "hello world"; // 自带 \0
char str2[20] = "---------------"; // 用来观察覆盖效果

printf("===== char数组拷贝(重点:\\0问题) =====\n");
printf("拷贝前 str2 = %s\n", str2);

// 只拷贝 5 个字节:'h','e','l','l','o'
// 注意:没有拷贝字符串末尾的 \0 !!!
memcpy(str2, str1, 5 * sizeof(char));

printf("拷贝后 str2 = %s\n", str2);
printf("原因:memcpy 只拷贝 5 个字节,不会自动加 \\0,后面的内容保留!\n");

        还可以拷贝结构体:

// ==========================
// 2. memcpy 拷贝 结构体
// ==========================
struct Student stu1 = { 1001, 20, "ZhangSan" };
struct Student stu2;

// 直接拷贝整个结构体的内存
memcpy(&stu2, &stu1, sizeof(struct Student));

printf("===== 结构体拷贝 =====\n");
printf("学号:%d\n年龄:%d\n姓名:%s\n\n", stu2.id, stu2.age, stu2.name);

由此可见,memcpy的操作他是这样的,图示:

1.1.2 memcpy的模拟实现

        如果这个函数功能要由我们自己来实现呢,对一个接口进行模拟实现,首先看的就是他的参数和返回值,参数这里不再赘述,memcpy的返回值是:

        也就是目标内存块的起始地址

        对memcpy的模拟实现,他既然要操作num个字节,要想一个字节一个字节的操作,我们肯定是要借助char*,实现如下:

void* my_memcpy(void* des, const void* src, size_t num)
{
    // 你给我传进来的两个指针不能为空
    assert(des && src);

    // 一个字节一个字节拷贝,总共有num个字节
    for (size_t i = 0; i < num; ++i)
        ((char*)des)[i] = ((char*)src)[i];

    // 返回值要是目标内存块的起始地址
    return des;
}

        这个函数的模拟实现整体还是非常简单的,测试一下,将刚刚的测试用例全部换成我们自己实现的memcpy:

// ==========================
// 1. memcpy 拷贝 int 数组
// ==========================
int arr1[5] = { 10, 20, 30, 40, 50 };
int arr2[5] = { 0 }; // 初始化为0

// 拷贝 5个int 的内存
my_memcpy(arr2, arr1, 5 * sizeof(int));

printf("===== int数组拷贝 =====\n");
for (int i = 0; i < 5; i++) {
    printf("%d ", arr2[i]);
}
printf("\n\n");


// ==========================
// 2. memcpy 拷贝 结构体
// ==========================
struct Student stu1 = { 1001, 20, "ZhangSan" };
struct Student stu2;

// 直接拷贝整个结构体的内存
my_memcpy(&stu2, &stu1, sizeof(struct Student));

printf("===== 结构体拷贝 =====\n");
printf("学号:%d\n年龄:%d\n姓名:%s\n\n", stu2.id, stu2.age, stu2.name);


 //==========================
 //3. memcpy 拷贝 char数组
 //【重点:不处理 \0,只按字节拷贝】
 //==========================
char str1[20] = "hello world"; // 自带 \0
char str2[20] = "---------------"; // 用来观察覆盖效果

printf("===== char数组拷贝(重点:\\0问题) =====\n");
printf("拷贝前 str2 = %s\n", str2);

// 只拷贝 5 个字节:'h','e','l','l','o'
// 注意:没有拷贝字符串末尾的 \0 !!!
my_memcpy(str2, str1, 5 * sizeof(char));

printf("拷贝后 str2 = %s\n", str2);
printf("原因:memcpy 只拷贝 5 个字节,不会自动加 \\0,后面的内容保留!\n");

        结果非常完美,没有任何问题。

        但是但是,如果说我们要做的操作是这样呢?

        我想做的是一个内存块内部的拷贝,那我们刚刚实现的memcpy还能解决这个问题吗?

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    my_memcpy(arr + 2, arr, sizeof(int) * 5);

    return 0;
}

        很显然并不能,因为把每次拷贝的细节掰开来看的话,当我们将1拷贝到3, 2拷贝到4的位置的时候,原本的值就被覆盖了,等再想把3拷贝到5这个位置的时候,3已经不见了,取而代之的刚刚拷贝过来的1,于是就会出现这样的情况。

        在这种情况下,我们就引出了下面这个内存操作函数-----memmove

1.2 memmove

1.2.1 函数功能介绍与使用

返回值:返回目标内存块的起始地址。

功能:从 source 拷贝 num 个字节到 destination支持处理内存重叠场景

他的参数和memcpy一模一样,都是对内存块的操作,唯一的不同是:memmove 是一个支持内存重叠的安全版 memcpy,拷贝逻辑更鲁棒,代价是比 memcpy 略慢(多了方向判断和分支)。

函数的使用:

1. 将刚刚的情况换成memmove,还会出现刚刚的问题吗?

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    memmove(arr + 2, arr, sizeof(int) * 5);
    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));

    return 0;
}

答案是当然不会

 2. 再看一段测试用例:

int main()
{
    // 测试用数组(内存重叠场景)
    char arr[] = "abcdefghijklmn";

    printf("原字符串:%s\n", arr);

    // 核心:从 arr+2 拷贝 5 个字符到 arr 位置
    // 源和目标内存重叠,必须用 memmove!
    memmove(arr, arr + 2, 5);

    printf("移动后:%s\n", arr);

    return 0;
}

 memmove = 能安全处理内存重叠的 memcpy这就是它唯一、也是最重要的功能!

那这个函数,怎么进行模拟实现呢?

1.2.2 memmove的模拟实现

        memmove的模拟实现,牢记一个点,他的实现就会变得非常简单。在一个内存块上对数据的操作,势必会造成覆盖这种问题,从而导致结果出错,当你就是要做重叠操作的时候,覆盖是一定会发生的,我们可以发送覆盖,但要切记:

        一定不要覆盖,你将来会使用的数据。将来这个词很关键,也就是从侧面说明,你可以覆盖你已经用过的数据,这样结果就不会出问题了。

        所以刚刚那个例子,我们可以这样去操作:

        从后往前拷贝,这样不就不会被覆盖了吗。

        可是,一味的从后往前拷贝,就不会出问题吗?答案是否定的,一定会出问题,看下面这种场景:

        我想把后面的3,4,5,6,7,挪到数组最开始的位置,变成3,4,5,6,7,5,6,8,9,10。

从后往前挪?能做到吗?

        这种情况是做不到的,因为在7挪到5的位置的时候,5还没有被使用,这就造成了破坏性的覆盖,6的道理也是一样的。所以这种情况下从后往前挪就不行。

        因此,在这种情况下,需要从前往后挪,先挪3,7最后挪,中间当5把3覆盖的时候,3已经被使用,不怕覆盖,6覆盖4,7覆盖5也是同样的道理。

        经过上述分析,memmove的模拟实现的总体思路就已经全部出来了,非常直观:

        当dst在src之前的时候,我们从前往后拷贝,不怕覆盖,当dst在src之后的时候,我们从后往前拷贝,也不怕覆盖:

void* my_memmove(void* des, const void* src, size_t num)
{
    assert(des && src);

    // memmove的模拟实现,主要就是抓住一个点:你不要覆盖自己将来可能会用到的数据

    // 当des再src之前,正常从前往后拷贝
    if ((char*)des < (char*)src)
    {
        for (size_t i = 0; i < num; ++i)
            ((char*)des)[i] = ((char*)src)[i];
    }
    else
    {
        // 这个地方的i一定不能用size_t类型,要不然i>=0的边界是一定会出问题的,会死循环
        for (int i = num - 1; i >= 0; --i)
            ((char*)des)[i] = ((char*)src)[i];
    }

    return des;
}

        测试一下:

int main()
{
    // 测试用数组(内存重叠场景)
    char arr1[] = "abcdefghijklmn";

    printf("原字符串:%s\n", arr1);

    // 核心:从 arr+2 拷贝 5 个字符到 arr 位置
    // 源和目标内存重叠,必须用 memmove!
    my_memmove(arr1, arr1 + 2, 5);

    printf("移动后:%s\n", arr1);

    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    my_memmove(arr + 2, arr, sizeof(int) * 5);
    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));

    return 0;
}

        没有任何问题,完美实现

        从模拟实现也可以看出来,就算内存没有重叠,memmove也完全可用,他比memcpy更安全,就是会稍微慢一点,毕竟安全的代价当然就是实现起来更复杂一点点。

1.3 一种现象

        看这段代码:

int main()
{
    // 同一数组,内存高度重叠
    char arr[30] = "abcdefghijklmn";

    printf("修改前:%s\n", arr);

    // 危险操作:目标地址 < 源地址,内存重叠!
    // 标准说 memcpy 行为未定义
    memcpy(arr, arr + 2, 5);

    printf("修改后:%s\n", arr);

    return 0;
}

        这是C标准库里实现的memcpy,可以很明显的看到,memcpy对重叠内存块的操作,也是没有任何问题的,由此我们可以大胆推测:

        C标准库的memcpy的实现,是采用的memmove的实现方式,他给你把重叠问题也给处理了。

        这种实现是没有任何问题的,memcpy只需要把内存块不重叠的情况解决就可以了,但是他帮你把重叠的也给解决了。就是说老师只需要你考60分,但是你考了100分,行还不是不行?太行了!所以这一点大家也要注意

        剩下的内存操作函数,memset,cmp,chr就很简单了,功能,作用都很单一。

1.4 memset

返回值:返回内存块的起始地址 ptr

核心功能:将 ptr 指向的内存块的前 num 个字节,全部设置为 value 对应的字节值(按 unsigned char 解释)。

逐字节填充:它是按字节操作的,value 最终会被截断为 unsigned char 类型,只取低 8 位。比如 memset(arr, 0x1234, 4),实际填充的是 0x34 这个字节。这是历史的产物,也可以说是C语言的一个小缺陷。

无视类型:不管 ptr 指向的是 char[]int[] 还是结构体,都按字节填充,因此常用来初始化 / 清零内存

无终止符检查:和 memcpy 一样,只看 num 字节,不管 \0

典型使用场景:

int main()
{
    int arr[5];
    // 1. 数组清零(最常用)
    memset(arr, 0, sizeof(arr));

    char str[10];
    // 2. 填充指定字符
    memset(str, '#', sizeof(str)-1);
    str[9] = '\0'; // 手动补结束符

    printf("str = %s\n", str);
    return 0;
}

        模拟实现:

void* my_memset(void* ptr, int value, size_t num)
{
    assert(ptr);
    unsigned char* p = (unsigned char*)ptr;
    unsigned char val = (unsigned char)value;

    for (size_t i = 0; i < num; ++i)
    {
        p[i] = val;
    }
    return ptr;
}

正确用法:只用来清零填充单字节值

1.5 memcmp

功能 :比较 ptr1ptr2 指向的两块内存的前 num 个字节,按无符号字符逐字节对比,返回表示大小关系的结果。

关键特性:

不终止于 \0:与 strcmp 最大的区别是,它不会因为遇到 \0 就停止比较,会严格对比指定的 num 个字节。

按字节无符号比较:比较时按 unsigned char 解释每个字节的值,因此 0xFF 会被认为大于 0x00

返回值含义

返回值含义
< 0ptr1 中第一个不匹配的字节值小于 ptr2 中的对应字节
= 0两块内存的前 num 个字节完全相同
> 0ptr1 中第一个不匹配的字节值大于 ptr2 中的对应字节

使用场景:

int main()
{
    char str1[] = "abc\0def";
    char str2[] = "abc\0xyz";

    // 对比前 5 个字节,不受中间 \0 影响
    int ret = memcmp(str1, str2, 5);

    if (ret == 0)
        printf("两块内存前5个字节相同\n");
    else if (ret < 0)
        printf("str1 小于 str2\n");
    else
        printf("str1 大于 str2\n");

    return 0;
}

模拟实现:

int my_memcmp(const void *ptr1, const void *ptr2, size_t num)
{
    const unsigned char *p1 = (const unsigned char *)ptr1;
    const unsigned char *p2 = (const unsigned char *)ptr2;

    for (size_t i = 0; i < num; ++i)
    {
        if (p1[i] != p2[i])
        {
            return p1[i] - p2[i];
        }
    }
    return 0;
}

注意!!!

不要用 memcmp 比较包含指针、浮点数等复杂类型的结构体:这些类型的字节表示可能有填充、对齐或实现差异,即使逻辑上相等,字节对比也可能失败。

只适合比较原始字节数据(如字节数组、字符串内容)。

1.6 memchr

功能:ptr 指向的内存块的num 个字节中,按无符号字节查找第一个与 value 匹配的字节,找到则返回该字节的地址,找不到返回 NULL

关键特性

逐字节无符号比较value 会被截断为 unsigned char 再参与比较,因此 memchr(arr, 0x1234, ...) 实际查找的是 0x34

无视 \0:和 strchr 最大的区别是,它不会因为遇到字符串结束符 \0 就停止,会严格搜索指定的 num 个字节。

找到即返回:只返回第一个匹配项的地址。

使用场景:

int main()
{
    char str[] = "abc\0defghij";

    // 在前 8 个字节中查找字符 'd'
    char *p = (char*)memchr(str, 'd', 8);

    if (p != NULL)
        printf("找到了:%s\n", p);
    else
        printf("没找到\n");

    return 0;
}

模拟实现:

void* my_memchr(const void *ptr, int value, size_t num)
{
    const unsigned char *p = (const unsigned char*)ptr;
    unsigned char val = (unsigned char)value;

    for (size_t i = 0; i < num; ++i)
    {
        if (p[i] == val)
        {
            return (void*)(p + i);
        }
    }
    return NULL;
}

2. 总结

函数核心功能关键特性
memcpy内存拷贝不处理重叠,效率高
memmove内存移动支持重叠拷贝,更安全
memset内存填充按字节设置值,常用于清零
memcmp内存比较逐字节对比,无视 \0
memchr内存查找按字节搜索,无视 \0

这五个函数的本质都是按字节操作内存,区别只在于:

拷贝 / 移动:处理数据位置

填充:批量设置字节值

比较 / 查找:逐字节对比和搜索

它们都无视数据类型,也不关心字符串结束符 \0,这是和字符串函数最核心的区别。

附录:完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <string.h>

//int main()
//{
//	char str1[] = "hello world!";
//	char str2[20];
//	printf("str1 = %s\n", str1);
//	strcpy(str2, str1);
//	printf("str2 = %s\n", str1);
//
//	return 0;
//}

void PrintArr(int* arr, size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//int main()
//{
//
//
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int cp_arr[10] = { 0 };
//
//	/*for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
//	{
//		cp_arr[i] = arr[i];
//	}*/
//
//	memcpy(cp_arr, arr, sizeof(arr));
//
//	printf("arr: ");
//	PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//	
//	printf("cp_arr: ");
//	PrintArr(cp_arr, sizeof(cp_arr) / sizeof(cp_arr[0]));
//
//	return 0;
//}

#include <assert.h>
void* my_memcpy(void* des, const void* src, size_t num)
{
    // 你给我传进来的两个指针不能为空
    assert(des && src);

    // 一个字节一个字节拷贝,总共有num个字节
    for (size_t i = 0; i < num; ++i)
        ((char*)des)[i] = ((char*)src)[i];

    // 返回值要是目标内存块的起始地址
    return des;
}

//// 定义一个测试用结构体
//struct Student {
//    int id;
//    int age;
//    char name[20];
//};
//
//int main() {
//    // ==========================
//    // 1. memcpy 拷贝 int 数组
//    // ==========================
//    int arr1[5] = { 10, 20, 30, 40, 50 };
//    int arr2[5] = { 0 }; // 初始化为0
//
//    // 拷贝 5个int 的内存
//    my_memcpy(arr2, arr1, 5 * sizeof(int));
//
//    printf("===== int数组拷贝 =====\n");
//    for (int i = 0; i < 5; i++) {
//        printf("%d ", arr2[i]);
//    }
//    printf("\n\n");
//
//
//    // ==========================
//    // 2. memcpy 拷贝 结构体
//    // ==========================
//    struct Student stu1 = { 1001, 20, "ZhangSan" };
//    struct Student stu2;
//
//    // 直接拷贝整个结构体的内存
//    my_memcpy(&stu2, &stu1, sizeof(struct Student));
//
//    printf("===== 结构体拷贝 =====\n");
//    printf("学号:%d\n年龄:%d\n姓名:%s\n\n", stu2.id, stu2.age, stu2.name);
//
//
//     //==========================
//     //3. memcpy 拷贝 char数组
//     //【重点:不处理 \0,只按字节拷贝】
//     //==========================
//    char str1[20] = "hello world"; // 自带 \0
//    char str2[20] = "---------------"; // 用来观察覆盖效果
//
//    printf("===== char数组拷贝(重点:\\0问题) =====\n");
//    printf("拷贝前 str2 = %s\n", str2);
//
//    // 只拷贝 5 个字节:'h','e','l','l','o'
//    // 注意:没有拷贝字符串末尾的 \0 !!!
//    my_memcpy(str2, str1, 5 * sizeof(char));
//
//    printf("拷贝后 str2 = %s\n", str2);
//    printf("原因:memcpy 只拷贝 5 个字节,不会自动加 \\0,后面的内容保留!\n");
//
//    return 0;
//}

//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    my_memcpy(arr + 2, arr, sizeof(int) * 5);
//    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//
//    return 0;
//}

//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    memmove(arr + 2, arr, sizeof(int) * 5);
//    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//
//    return 0;
//}

void* my_memmove(void* des, const void* src, size_t num)
{
    assert(des && src);

    // memmove的模拟实现,主要就是抓住一个点:你不要覆盖自己将来可能会用到的数据

    // 当des再src之前,正常从前往后拷贝
    if ((char*)des < (char*)src)
    {
        for (size_t i = 0; i < num; ++i)
            ((char*)des)[i] = ((char*)src)[i];
    }
    else
    {
        // 这个地方的i一定不能用size_t类型,要不然i>=0的边界是一定会出问题的,会死循环
        for (int i = num - 1; i >= 0; --i)
            ((char*)des)[i] = ((char*)src)[i];
    }

    return des;
}

//int main()
//{
//    // 测试用数组(内存重叠场景)
//    char arr1[] = "abcdefghijklmn";
//
//    printf("原字符串:%s\n", arr1);
//
//    // 核心:从 arr+2 拷贝 5 个字符到 arr 位置
//    // 源和目标内存重叠,必须用 memmove!
//    my_memmove(arr1, arr1 + 2, 5);
//
//    printf("移动后:%s\n", arr1);
//
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    my_memmove(arr + 2, arr, sizeof(int) * 5);
//    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//
//    return 0;
//}

//int main()
//{
//    // 同一数组,内存高度重叠
//    char arr[30] = "abcdefghijklmn";
//
//    printf("修改前:%s\n", arr);
//
//    // 危险操作:目标地址 < 源地址,内存重叠!
//    // 标准说 memcpy 行为未定义
//    memcpy(arr, arr + 2, 5);
//
//    printf("修改后:%s\n", arr);
//
//    return 0;
//}

//int main()
//{
//    int arr[5];
//    // 1. 数组清零(最常用)
//    memset(arr, 0, sizeof(arr));
//
//    char str[10];
//    // 2. 填充指定字符
//    memset(str, '#', sizeof(str) - 1);
//    str[9] = '\0'; // 手动补结束符
//
//    printf("str = %s\n", str);
//    return 0;
//}

//void* my_memset(void* ptr, int value, size_t num)
//{
//    assert(ptr);
//    unsigned char* p = (unsigned char*)ptr;
//    unsigned char val = (unsigned char)value;
//
//    for (size_t i = 0; i < num; ++i)
//    {
//        p[i] = val;
//    }
//    return ptr;
//}

//int main()
//{
//    char str1[] = "abc\0def";
//    char str2[] = "abc\0xyz";
//
//    // 对比前 5 个字节,不受中间 \0 影响
//    int ret = memcmp(str1, str2, 5);
//
//    if (ret == 0)
//        printf("两块内存前5个字节相同\n");
//    else if (ret < 0)
//        printf("str1 小于 str2\n");
//    else
//        printf("str1 大于 str2\n");
//
//    return 0;
//}

int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
    const unsigned char* p1 = (const unsigned char*)ptr1;
    const unsigned char* p2 = (const unsigned char*)ptr2;

    for (size_t i = 0; i < num; ++i)
    {
        if (p1[i] != p2[i])
        {
            return p1[i] - p2[i];
        }
    }
    return 0;
}

int main()
{
    char str[] = "abc\0defghij";

    // 在前 8 个字节中查找字符 'd'
    char* p = (char*)memchr(str, 'd', 8);

    if (p != NULL)
        printf("找到了:%s\n", p);
    else
        printf("没找到\n");

    return 0;
}

void* my_memchr(const void* ptr, int value, size_t num)
{
    const unsigned char* p = (const unsigned char*)ptr;
    unsigned char val = (unsigned char)value;

    for (size_t i = 0; i < num; ++i)
    {
        if (p[i] == val)
        {
            return (void*)(p + i);
        }
    }
    return NULL;
}

内容概要:本文详细记录了对一个Android ARM64静态ELF文件中字符串加密机制的逆向分析过程。该ELF文件的所有字符串均被加密,无法通过常规strings命令或IDA直接识别。作者通过分析发现,加密字符串存储在.rodata段,其解密所需信息(包括密文地址、长度和16位密钥)保存在.data.rel.ro段的40字节描述符中。核心解密函数sub_10F408采用自反的双pass流密码算法,结合固定密钥KEY_TERM(由.data段24字节数据计算得出),实现字节级非线性、位置与长度相关的加密。文章还复现了完整的Python解密脚本,并揭示了该保护机制的本质为代码混淆而非强加密,最终成功批量解密全部956条字符串,暴露程序真实行为,如shell命令模板、设备标识篡改、网络重置等操作。此外,文中还提及未启用的自定义壳框架及其反dump设计。; 适合人群:具备逆向工程基础的安全研究人员、二进制分析人员及对ELF保护技术感兴趣的开发者。; 使用场景及目标:①学习ELF二进制中字符串加密的典型实现方式与逆向突破口;②掌握从结构识别、函数追踪到算法还原的完整逆向流程;③理解“绑定二进制”的完整性校验设计及其局限性;④实践编写IDAPython脚本自动化提取与解密敏感数据。; 阅读建议:此资源以实战案例驱动,不仅展示技术细节,更强调逆向思维与验证方法,建议读者结合IDA调试环境,逐步跟随文中步骤进行动态分析与算法验证,深入理解每一步的推理依据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值