预先了解
"\0" 标志
-
它是 一个转义字符(escape character),表示的是 数值为 0 的字符,
\0
就是 一个字节值为 0 的字符。char str[] = "ABC"; //在 C语言的字符串 中,\0 用来表示 字符串的结束符
A B C \0 65 66 67 0 //因此 memset(ptr, 0, 100); //等价于 memset(ptr, '\0', 100); //"\0" 是 字符常量(值为0) //0 是 整数常量(值为0)
-
\0
是一个“值为0的字符”,在字符串里表示“结尾”,在内存操作函数里(如 memset)就是一个普通的 0 字节。
参数 dest(destination)
-
目的地地址,常用于 拷贝、写入类函数 中,意思是:“我要把数据拷贝到哪里去”;它是你要写入数据的目标内存地址。
memcpy(dest, src, n); //把 src 里的 n 个字节拷贝到 dest 去 //dest = 拷贝到哪儿(目标地址) //src = 从哪儿拷贝(源地址)
参数 ptr(pointer)
-
一个普通的指针(指向某块内存),它不强调“源”还是“目的”,只是表示“这是一段内存的入口地址”,通常用于访问、修改、初始化。
memset(ptr, 0, 100); //把从 ptr 开始的 100 个字节,都填上 0 //ptr = 这块内存的起点又称基址(可能是结构体、数组、堆内存……) //等价于 unsigned char *p = (unsigned char *)ptr; for (int i = 0; i < 100; i++) {*(p + i) = 0; //指针累加替换为0循环100次 }
步骤 内存地址 操作 内容 第1次 ptr + 0
→0x1000
写入 0 [0x1000] = 0
第2次 ptr + 1
→0x1001
写入 0 [0x1001] = 0
第3次 ptr + 2
→0x1002
写入 0 [0x1002] = 0
... 第100次 ptr + 100
→0x1099
写入 0 [0x1099] = 0
//举例1 int a = 10; //a 是一个变量,放在栈上 int *ptr = &a; //&a 是 a 的地址,ptr 存的就是这个地址 //所以 ptr = &a = 0x7ffeeabc1234(例如) //ptr 就是一张地图,上面写着这一块内存从哪里开始 ================================ //举例2 char buffer[100]; char *ptr = buffer; //ptr 就是 buffer 的基址,也就是说:ptr == &buffer[0] //可以用 ptr + 1、ptr + 2 来访问后续字节 char *arr = buffer; //这里arr等价ptr同为基址
内存重叠
-
用
memcpy()
或memmove()
拷贝数据时, 如果 源地址(src) 和 目标地址(dest) 在内存中 部分重叠, 就称为 内存重叠(overlapping memory region)。例如以下数组char s[10] = "ABCDEFG"; // 内存布局(每个字符占一个字节)
下标 0 1 2 3 4 5 6 7 值 A B C D E F G \0 //执行函数 memcpy(s + 2, s, 5); //把从 s[0] 开始的 5 个字节 (A B C D E),拷贝到 s[2] 开始的位置(目标区域) ==================== //在拷贝过程中,源和目标区域部分重叠。 源区间: s[0]..s[4] 目标区间: s[2]..s[6] ==================== //重叠部分 s[2]..s[4](既是源的一部分,也是目标的一部分) //memcpy() 的行为在这种情况下是 未定义的(undefined behavior)。 //有可能结果变成以下情况 AACDEFG //也可能变成别的(取决于编译器实现)
-
如果使用 memmove 函数:
memmove(s + 2, s, 5); //memmove 会检测是否有重叠,如果有,决定安全的拷贝方向
步骤 拷贝字节 结果 1 s[4]→s[6] A B C D E E F G 2 s[3]→s[5] A B C D D E F G 3 s[2]→s[4] A B C C D E F G 4 s[1]→s[3] A B B C D E F G 5 s[0]→s[2] A A B C D E F G //最终结果 A A B C D E F G //程序行为完全可预测,数据正确 ======================== //内存重叠可视化 源地址: [A][B][C][D][E] 目标地址: [ ][ ][ ][ ][ ]↑拷贝到这里(有重叠) ======================== //判断内存是否重叠的通用逻辑(额外) //只要 两段区间有交叉,就算重叠。 //因此,我们要判断区间 [src, src+n) 和 [dest, dest+n) 是否相交 //额外知识点:[a, b)是一种表达式,表示 “左闭右开区间”。包含 a,不包含 b。[src, src+n)也就是说,范围包括起始地址 src,但不包括 src + n (src < dest + n) && (dest < src + n)
-
总结:
- memcpy 快,但危险;memmove 稳,但稍慢。
memmove()
:智能处理重叠,先从后面拷贝(能完整保留复制的内存块,拷贝结果总是正确的)memcpy()
:直接按顺序复制,可能覆盖源数据(属于未定义行为,不同编译器下结果可能不同)
-
核心内存操作函数(直接操作字节)
函数名 | 原型 | 参数解释 | 功能说明 | 示例 | 返回值 / 注意事项 |
---|---|---|---|---|---|
memcpy | void *memcpy(void *dest, const void *src, size_t n); |
dest:目标地址;src:源地址;n:要复制的字节数 | 从 src 拷贝 n 字节到 dest(不支持重叠) | memcpy(b, a, 5); |
返回 dest 指针❗内存不能重叠 |
memmove | void *memmove(void *dest, const void *src, size_t n); |
同上 | 从 src 拷贝 n 字节到 dest(支持重叠) | memmove(s+2, s, 4); |
返回 dest✅ 重叠安全 |
memset | void *memset(void *ptr, int value, size_t n); |
ptr:目标内存;value:填充值(0–255);n:字节数 | 将内存全部填为某个字节值 | memset(arr, 0, sizeof(arr)); |
返回 ptr❗按字节填充 |
memcmp | int memcmp(const void *ptr1, const void *ptr2, size_t n); |
ptr1、ptr2:两块内存;n:比较字节数 | 比较两块内存内容 | memcmp(a, b, 3); |
0=相等<0=小于>0=大于 |
扩展内存函数(安全或特殊用途)
函数名 | 原型 | 参数解释 | 功能说明 | 示例 | 返回值 / 注意事项 |
---|---|---|---|---|---|
memchr | void *memchr(const void *ptr, int value, size_t n); |
ptr:要搜索的内存;value:目标字节;n:范围 | 查找指定字节 | memchr(arr, 0x20, len); |
返回找到的地址或 NULL |
memcpy_s | errno_t memcpy_s(void *dest, size_t destSize, const void *src, size_t n); |
dest、src、n 同上;destSize:目标总大小 | 安全版 memcpy,防越界 | memcpy_s(b, sizeof(b), a, len); |
成功返回 0 |
memmove_s | errno_t memmove_s(void *dest, size_t destSize, const void *src, size_t n); |
同上 | 安全版 memmove | memmove_s(s+2,10,s,4); |
成功返回 0 |
memset_s | errno_t memset_s(void *ptr, size_t n, int value, size_t nmax); |
ptr:目标;value:填充值;n:操作字节数 | 安全清空内存 | memset_s(pw, len, 0, len); |
常用于清除密码 |
动态内存分配函数(操作堆内存)
函数名 | 原型 | 参数解释 | 功能说明 | 示例 | 返回值 / 注意事项 |
---|---|---|---|---|---|
malloc | void *malloc(size_t size); |
size:要分配的字节数 | 在堆上分配指定大小的内存 | int *p = malloc(10*sizeof(int)); |
返回指针或 NULL |
calloc | void *calloc(size_t n, size_t size); |
n:元素个数;size:每个元素大小 | 分配并自动清零 | int *p = calloc(10,sizeof(int)); |
返回指针或 NULL |
realloc | void *realloc(void *ptr, size_t new_size); |
ptr:原内存;new_size:新大小 | 重新调整内存大小 | p = realloc(p, 20*sizeof(int)); |
返回新地址或 NULL |
free | void free(void *ptr); |
ptr:要释放的内存 | 释放 malloc/calloc/realloc 分配的内存 | free(p); p = NULL; |
❗释放后别再访问 |
字符串相关函数(本质是内存操作)
函数名 | 原型 | 参数解释 | 功能说明 | 示例 | 返回值 / 注意事项 |
---|---|---|---|---|---|
strcpy | char *strcpy(char *dest, const char *src); |
dest:目标字符串;src:源字符串 | 拷贝直到遇到 \0 |
strcpy(a, b); |
返回 dest❗需要足够空间 |
strncpy | char *strncpy(char *dest, const char *src, size_t n); |
同上;n:最大拷贝数 | 拷贝 n 个字符,不一定补 \0 |
strncpy(a, b, 3); |
可能导致未终止字符串 |
strcat | char *strcat(char *dest, const char *src); |
dest:目标字符串;src:源字符串 | 拼接字符串 | strcat(a, "abc"); |
返回 dest❗空间要足够 |
strlen | size_t strlen(const char *s); |
s:字符串 | 计算长度(不含 \0 ) |
len = strlen("Hi"); |
返回字符数 |
strcmp | int strcmp(const char *s1, const char *s2); |
s1, s2:要比较的字符串 | 按字符逐个比较 | strcmp("abc", "abd"); |
返回 0、<0、>0 |
strchr | char *strchr(const char *s, int c); |
s:字符串;c:要查找的字符 | 查找字符 c | strchr("abc",'b'); |
返回指针或 NULL |
strstr | char *strstr(const char *s1, const char *s2); |
s1:主串;s2:子串 | 查找子串 | strstr("abcde","cd"); |
返回子串地址或 NULL |
总结
分类 | 常用函数 | 操作类型 | 特点 / 用途 |
---|---|---|---|
内存操作 | memcpy, memmove, memset, memcmp |
字节级 | 最底层内存操作 |
字符串操作 | strcpy, strcat, strlen, strcmp |
字符串 | 基于 char[] |
内存查找 | memchr, strstr |
查找特定字节或子串 | 用于数据扫描 |
动态内存 | malloc, calloc, realloc, free |
堆分配与释放 | 管理内存生命周期 |
安全函数 | _s 族 |
加边界检测 | 防越界、安全编程 |