C语言常用字符串与内存函数详解及模拟实现

周予安
周予安
发布时间:2026-10-16| 4人浏览过
原创

C语言开发中,字符串与内存操作是基础且高风险的领域。本文详细分析 strlen、strcpy、strcat、strcmp 等字符串函数的行为特征,以及 memcpy、memmove、memset 等内存操作函数的底层逻辑。重点解析无符号数比较陷阱、内存重叠拷贝的安全处理,并提供各函数的模拟实现源码,助你规避常见Bug。

一. strlen:长度计算与无符号数陷阱

size_t strlen ( const char * str ) 返回字符串中 '\0' 之前的字符个数。返回值类型为 size_t,即无符号整数。

这里存在一个经典陷阱:当计算两个不同长度字符串的长度差时,如果前一个较短,结果会因无符号数的下溢特性变成一个极大的正数。

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

int main() {
    // strlen("abc") = 3, strlen("abcdef") = 6
    // 3 - 6 = -3,但在无符号运算中变为极大值
    if (strlen("abc") - strlen("abcdef")) {
        printf(">\n");
    } else {
        printf("<\n");
    }
    return 0;
}

模拟实现:

size_t my_strlen(const char* str) {
    if (!str) return 0;
    size_t len = 0;
    while (*str++) {
        len++;
    }
    return len;
}

二. strcpy:不可逆的字符串拷贝

char* strcpy(char* destination, const char* source) 负责将源字符串拷贝到目标缓冲区,包括结尾的 '\0'。

  • 风险点:它不检查目标缓冲区大小,极易导致缓冲区溢出。使用前提是目标地址足够大且可写。

模拟实现:

char* my_strcpy(char* dest, const char* source) {
    assert(dest != NULL && source != NULL);
    char* ret = dest;
    while ((*dest++ = *source++) != '\0');
    return ret;
}

三. strcat:追加与边界检查

char* strcat(char* destination, const char* source) 将源字符串追加到目标字符串末尾。它会覆盖目标字符串原本的 '\0',并在追加结束后在末尾添加新的 '\0'。

注意:必须保证目标缓冲区有足够剩余空间。首先找到目标串的结束符,再进行拼接。

模拟实现:

char* my_strcat(char* dest, const char* src) {
    assert(dest && src);
    char* ret = dest;
    // 1. 定位到目标字符串末尾
    while (*dest) {
        dest++;
    }
    // 2. 进行拷贝
    while (*dest++ = *src++);
    return ret;
}

四. strcmp:逐字符比较规则

int strcmp(const char* s1, const char* s2) 并不是比较长度,而是从前往后逐个比较对应位置的 ASCII 码。

  • 相等继续下一位;遇到 '\0' 停止。
  • 若全部相等,返回 0。
  • 若 s1[i] > s2[i],返回大于 0 的值。
  • 若 s1[i] < s2[i],返回小于 0 的值。

模拟实现:

int my_strcmp(const char* s1, const char* s2) {
    assert(s1 && s2);
    while (*s1 == *s2) {
        if (*s1 == '\0') {
            return 0;
        }
        s1++;
        s2++;
    }
    // 根据差值的正负返回结果
    return (*(unsigned char*)s1) - (*(unsigned char*)s2);
}

五. strncpy 与 strncat:长度受限的操作

1. strncpy:有限拷贝

char* strncpy(char* dest, const char* src, size_t count) 仅拷贝前 count 个字符。若源串长于 count,则不会在末尾补 '\0',这在后续使用时可能引发错误,需手动确保终止。

2. strncat:有限追加

char* strncat(char* dest, const char* src, size_t count) 追加最多 count 个字符,并强制在末尾添加一个 '\0'。这比 strcpy 更安全,但依然需要确保 dest 有足够的总空间容纳原内容+count+'\0'。

strncat 模拟实现:

char* my_strncat(char* dest, const char* src, size_t count) {
    assert(dest && src);
    char* ret = dest;
    // 找到末尾
    while (*dest) dest++;
    // 拷贝指定数量
    while (count-- && (*dest++ = *src++));
    // 补充终止符
    *dest = '\0';
    return ret;
}

六. strstr:子串查找

const char* strstr(const char* str1, const char* str2) 用于查找 str2 作为子串在 str1 中首次出现的位置。若未找到,返回 NULL;找到则返回指向该位置的指针。

七. strtok:字符串分割器

char* strtok(char* str, const char* sep) 利用分隔符集合 sep 切割字符串。其内部维护了一个静态状态变量。

  • 第一次调用:传入待处理的 str,它在第一个分隔符处插入 '\0' 并返回片段起始地址。
  • 后续调用:第一个参数传 NULL,函数将从上次断点继续向后扫描。
  • 完成:若无更多片段,返回 NULL。

循环分割示例:

char a[] = "A@nice.boy";
const char sep[] = "@.";
char* token = strtok(a, sep);
while (token != NULL) {
    printf("%s\n", token);
    token = strtok(NULL, sep); // 下次用 NULL 继续
}

八. strerror:错误码转换

char* strerror(int errnum) 接受系统错误代码(通常来自全局变量 errno),返回对应的可读英文错误描述字符串。主要用于调试或向用户展示具体错误原因。

九. memcpy vs memmove:内存移动的核心差异

1. memcpy:普通拷贝

void* memcpy(void* dest, const void* src, size_t count) 按字节拷贝。它假设源和目标区域不重叠。执行方向通常是从低地址向高地址。

模拟实现:

void* my_memcpy(void* dest, const void* src, size_t count) {
    assert(dest && src);
    void* ret = dest;
    for (size_t i = 0; i < count; ++i) {
        ((char*)dest)[i] = ((const char*)src)[i];
    }
    return ret;
}

2. memmove:处理重叠

void* memmove(void* dest, const void* src, size_t count) 专门用于处理源与目标内存区域重叠的情况,保证数据不被提前覆盖。

  • 目标在源右侧(dest > src):从后往前拷贝(高地址到低地址),防止数据覆盖。
  • 目标在源左侧(dest <= src):从前往后拷贝即可。

模拟实现:

void* my_memmove(void* dest, const void* src, size_t count) {
    assert(dest && src);
    char* d = (char*)dest;
    const char* s = (const char*)src;
    
    if (d < s || d == s) {
        // 正常顺序拷贝
        while (count--) {
            *d++ = *s++;
        }
    } else {
        // 倒序拷贝,防止重叠区域数据被覆盖
        d += count - 1;
        s += count - 1;
        while (count--) {
            *d-- = *s--;
        }
    }
    return dest;
}

十. memcmp 与 memset

1. memcmp:按字节比较

int memcmp(const void* s1, const void* s2, size_t count) 比较前 count 个字节的二进制内容。常用于比较结构体实例是否相等。

2. memset:内存填充

void* memset(void* dest, int c, size_t count) 将目标内存的前 count 个字节设置为指定值 c。
注意:由于是按字节赋值,设置非零值(如 1)时,每个字节都会变成 0x01,整型变量的值将是 0x01010101,而非数字 1。清零(设为 0)是唯一安全的通用操作。

总结

掌握这些函数的模拟实现,不仅能应对面试考察,更能让我们在编写生产代码时,清晰理解内存管理的细节。在处理字符串和内存块时,始终优先考虑缓冲区边界检查和数据重叠情况,以保障程序的稳定性。

以上就是C语言字符串与内存函数详解及模拟实现的详细内容,更多关于C语言函数安全使用的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。