C语言字符串与内存操作函数模拟实现详解

一、strstr函数模拟实现

1.1 函数功能

strstr用于在字符串 str1中查找子串 str2的首次出现位置:

复制代码
const char* strstr(const char* str1, const char* str2);
char* strstr(char* str1, const char* str2);
  • 找到则返回第一次出现的起始地址

  • 找不到则返回NULL

1.2 实现原理

场景1:简单匹配
复制代码
char arr[] = "abcdefabcdef";
char* p = "cdef";
char* ret = my_strstr(arr, p);  // 返回 "cdefabcdef" 的起始地址
场景2:多次匹配(复杂情况)
复制代码
str1: "abbbcdef\0"
str2: "bbc\0"

可能存在多次匹配尝试,需要逐字符比较

1.3 模拟实现代码

复制代码
char* my_strstr(const char* str1, const char* str2) {
    assert(str1 && str2);
    
    const char* cur = str1;  // 当前查找位置
    const char* s1 = NULL;
    const char* s2 = NULL;
    
    while (*cur != '\0') {
        s1 = cur;
        s2 = str2;
        
        // 逐字符比较
        while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) {
            s1++;
            s2++;
        }
        
        // 如果str2完全匹配成功
        if (*s2 == '\0') {
            return (char*)cur;  // 返回找到的位置
        }
        
        cur++;  // 继续下一个位置
    }
    
    return NULL;  // 未找到
}

二、memcpy函数模拟实现

2.1 函数功能

复制代码
void* memcpy(void* destination, const void* source, size_t num);
  • 将源内存区域的num个字节拷贝到目标内存区域

  • 返回目标空间的起始地址

  • 不负责处理重叠内存的拷贝

2.2 实现原理

复制代码
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[20] = {0};
my_memcpy(arr2, arr1+2, 20);  // 拷贝arr1中的3,4,5,6,7

内存图示:

复制代码
源地址(src): arr1+2 → 元素3的位置
目标地址(dest): arr2 → 起始位置
拷贝大小: 20字节 (5个int, 假设int为4字节)

2.3 模拟实现代码

复制代码
void* my_memcpy(void* dest, const void* src, size_t num) {
    void* ret = dest;
    assert(dest && src);
    
    // 逐字节拷贝
    for (size_t i = 0; i < num; i++) {
        *(char*)dest = *(char*)src;
        dest = (char*)dest + 1;
        src = (char*)src + 1;
    }
    
    return ret;
}

三、memmove函数模拟实现

3.1 函数功能

复制代码
void* memmove(void* destination, const void* source, size_t num);
  • memcpy功能类似,但能够处理内存重叠的情况

  • C语言标准规定memcpy负责不重叠内存拷贝,memmove负责重叠内存拷贝

3.2 内存重叠问题

复制代码
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
my_memmove(arr1 + 2, arr1, 20);  // 将前5个元素拷贝到从索引2开始的位置

拷贝前:

复制代码
索引: 0  1  2  3  4  5  6  7  8  9
值:   1  2  3  4  5  6  7  8  9  10

错误拷贝方式(从前向后):

复制代码
arr1[2] = arr1[0] → 1
arr1[3] = arr1[1] → 2  
arr1[4] = arr1[2] → 1 (已经被覆盖!)
arr1[5] = arr1[3] → 2 (已经被覆盖!)
...

3.3 模拟实现代码(正确处理重叠)

复制代码
void* my_memmove(void* dest, const void* src, size_t num) {
    void* ret = dest;
    assert(dest && src);
    
    // 情况1:dest在src前面,或内存不重叠 → 从前向后拷贝
    if (dest < src) {
        char* d = (char*)dest;
        const char* s = (const char*)src;
        while (num--) {
            *d++ = *s++;
        }
    }
    // 情况2:dest在src后面,有重叠风险 → 从后向前拷贝
    else {
        char* d = (char*)dest + num - 1;  // 指向目标末尾
        const char* s = (const char*)src + num - 1;  // 指向源末尾
        while (num--) {
            *d-- = *s--;
        }
    }
    
    return ret;
}

四、关键区别与总结

4.1 strstr vs mem系列函数

函数 操作对象 返回类型 主要用途
strstr 字符串 char* 查找子串
memcpy 内存块 void* 非重叠内存拷贝
memmove 内存块 void* 任意内存拷贝(含重叠)

4.2 memcpy vs memmove

  1. 标准规定

    • memcpy:只需实现不重叠内存拷贝

    • memmove:必须处理重叠内存拷贝

  2. 实际实现

    • 某些编译器(如VS)的memcpy也能处理重叠内存

    • 但为保证可移植性,重叠时应使用memmove

  3. 性能考虑

    • 不重叠时,两者性能相近

    • 重叠时,memmove会进行方向判断,略有开销

4.3 使用建议

  1. 查找字符串子串 → 使用strstr

  2. 拷贝不重叠的内存块 → 使用memcpy(性能可能略优)

  3. 不确定内存是否重叠 → 使用memmove(更安全)

  4. 需要自己实现时 → 参考上述代码,注意处理所有边界情况

五、完整测试示例

复制代码
#include <stdio.h>
#include <string.h>
#include <assert.h>

// 测试代码
int main() {
    // 1. 测试my_strstr
    char str[] = "abcdefabcdef";
    char sub[] = "cdef";
    char* result = my_strstr(str, sub);
    printf("strstr测试: %s\n", result ? result : "未找到");
    
    // 2. 测试my_memcpy
    int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
    int arr2[20] = {0};
    my_memcpy(arr2, arr1+2, 20);
    printf("memcpy测试: ");
    for(int i = 0; i < 5; i++) printf("%d ", arr2[i]);
    printf("\n");
    
    // 3. 测试my_memmove(重叠)
    int arr3[] = {1,2,3,4,5,6,7,8,9,10};
    my_memmove(arr3+2, arr3, 20);
    printf("memmove测试: ");
    for(int i = 0; i < 10; i++) printf("%d ", arr3[i]);
    printf("\n");
    
    return 0;
}

这些函数是C语言中处理字符串和内存的基础工具,理解它们的实现原理对于深入理解C语言内存管理和字符串操作至关重要。

相关推荐
薛定谔的悦2 小时前
储能 EMS 的功率策略:从一块电表到一次逆流的 200 毫秒
大数据·linux·能源·储能·bms
新时代牛马5 小时前
Linux 内核入门地图:架构、源码目录与五大子系统
linux·运维·架构
裕晟资质规划5 小时前
武器装备科研生产单位保密资质申请方法论:条件模型与流程拆解
人工智能·算法
Julien20046 小时前
控制 SELinux 文件上下文
linux·运维·服务器
EXI-小洲6 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
sulikey6 小时前
Linux Core Dump 完全指南:从原理到实战的崩溃调试手册。什么是core dump?程序报错core dumped怎么办?
linux·服务器·操作系统·调试·coredump
2601_961901707 小时前
SpringBoot使用Nacos进行application.yml配置管理
java·spring boot·spring
en.en..7 小时前
C语言 标准输入 / 输出缓冲区
算法
何以解忧,唯有..7 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
QQ_21696290968 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端