文章目录
- C语言内存操作与字符串处理
- 1 内存操作函数
- 1.1 普通版本
- 1.2 安全版本
- 2 字符串函数
- 2.1 普通版本
- 2.2 安全版本
- 3 其他安全替代方案
- 4 注意事项总结
- 5 最佳实践
C语言内存操作与字符串处理
C 语言中常见的内存操作和字符串处理函数
1 内存操作函数
1.1 普通版本
memcpy
- 内存复制
原型:
#include <string.h>
void* memcpy(void* dest, const void* src, size_t n);
功能:将 src
指向的内存区域的前 n
个字节复制到 dest
指向的内存区域。
参数:
dest
:目标内存地址。src
:源内存地址。n
:复制的字节数。
返回值:返回dest
的指针。
注意事项:- 内存重叠问题:若
src
和dest
的内存区域有重叠,行为未定义(需用memmove
替代)。 - 目标空间必须足够大,否则导致缓冲区溢出。
示例:
int src[] = {1, 2, 3};
int dest[3];
memcpy(dest, src, sizeof(src)); // 复制整个数组
memmove
- 安全内存复制
原型:
void* memmove(void* dest, const void* src, size_t n);
功能:与 memcpy
类似,但能正确处理内存重叠问题。
适用场景:当 src
和 dest
内存区域可能重叠时(如数组元素移位)。
示例:
char str[] = "Hello World";
memmove(str + 6, str, 5); // 将前5字节复制到第6字节位置
// 结果:str 变为 "Hello Hello"
memset
- 内存填充
原型:
void* memset(void* ptr, int value, size_t n);
功能:将 ptr
指向的内存区域的前 n
个字节设置为 value
(实际按字节填充)。
典型用途:
- 初始化数组为零:
memset(arr, 0, sizeof(arr))
。 - 设置内存块的特定模式。
注意事项:
value
的范围应为0~255
(一个字节)。
示例:
char buffer[10];
memset(buffer, 'A', 5); // 前5字节填充为 'A'
memset(buffer + 5, 0, 5); // 后5字节填充为0
1.2 安全版本
memcpy_s
(C11 标准)
原型:
#include <string.h> // C11 起支持
errno_t memcpy_s(void* dest, rsize_t dest_size, const void* src, rsize_t src_size);
功能:安全版本的 memcpy
,在复制前检查目标缓冲区大小。
参数:
dest_size
:目标缓冲区的总大小(字节)。src_size
:要复制的字节数(必须 ≤dest_size
且 ≤src
的实际大小)。
返回值:- 0:成功。
- 非零:失败(如参数无效或缓冲区溢出风险)。
示例:
char dest[10];
const char* src = "Hello";
errno_t err = memcpy_s(dest, sizeof(dest), src, 6); // 包括 '\0'
if (err != 0) { /* 处理错误 */ }
memset_s
(C11 标准)
原型:
errno_t memset_s(void* dest, rsize_t dest_size, int value, rsize_t count);
功能:安全版本的 memset
,检查填充范围是否超出目标缓冲区。
规则:
count
(填充字节数)必须 ≤dest_size
。- 若检测到错误(如
dest
为空或count
过大),可能触发运行时约束处理函数。
示例:
char buffer[10];
errno_t err = memset_s(buffer, sizeof(buffer), 0, sizeof(buffer)); // 安全清零
2 字符串函数
2.1 普通版本
strcpy
- 字符串复制
原型:
char* strcpy(char* dest, const char* src);
功能:将 src
指向的字符串(含 \0
)复制到 dest
。
注意事项:
- 高危函数:不检查
dest
的空间是否足够,易导致缓冲区溢出。 - 推荐使用更安全的
strncpy
或snprintf
。
示例:
char src[] = "Hello";
char dest[10];
strcpy(dest, src); // dest 内容为 "Hello\0"
strncpy
- 安全字符串复制
原型:
char* strncpy(char* dest, const char* src, size_t n);
功能:复制 src
的最多前 n
个字符到 dest
。
规则:
- 若
src
长度 >=n
:复制前n
个字符,不添加\0
。 - 若
src
长度 <n
:复制全部字符并补\0
至n
字节。
示例:
char dest[5];
strncpy(dest, "Hello World", sizeof(dest));
// dest 内容为 {'H','e','l','l','o'}(无终止符!)
strcat
/strncat
- 字符串拼接
原型:
char* strcat(char* dest, const char* src); // 危险:不检查长度
char* strncat(char* dest, const char* src, size_t n); // 安全:限制拼接长度
功能:将 src
字符串拼接到 dest
末尾(覆盖 dest
的 \0
)。
注意事项:
dest
必须足够大以容纳拼接后的结果。strncat
会自动添加终止符。
示例:
char dest[20] = "Hello";
strncat(dest, " World!", 7); // dest 变为 "Hello World!\0"
strcmp
/strncmp
- 字符串比较
原型:
int strcmp(const char* s1, const char* s2); // 比较整个字符串
int strncmp(const char* s1, const char* s2, size_t n); // 比较前n个字符
返回值:
- 0:字符串内容相同。
- 正/负数:根据 ASCII 码差异决定。
示例:
if (strcmp("apple", "apple") == 0) { /* 相等 */ }
if (strncmp("apple", "app", 3) == 0) { /* 前3字符相同 */ }
strlen
- 字符串长度
原型:
size_t strlen(const char* s);
功能:返回字符串长度(不含 \0
)。
注意:若 s
未以 \0
结尾,行为未定义。
示例:
int len = strlen("Hello"); // len = 5
strdup
- 字符串复制(动态分配)
原型:
char* strdup(const char* s); // 非标准但广泛支持
功能:复制字符串到新分配的内存(需手动 free
)。
示例:
char* copy = strdup("Hello");
free(copy); // 必须释放内存
2.2 安全版本
strcpy_s
(C11 标准)
原型:
errno_t strcpy_s(char* dest, rsize_t dest_size, const char* src);
功能:安全版本的 strcpy
,确保目标缓冲区足够容纳 src
(含终止符 \0
)。
规则:
dest_size
必须大于strlen(src)
。- 若
dest_size
不足,函数会将dest[0]
设为\0
并返回错误。
示例:
char dest[6];
errno_t err = strcpy_s(dest, sizeof(dest), "Hello");
if (err != 0) { /* 目标缓冲区过小 */ }
strncpy_s
(C11 标准)
原型:
errno_t strncpy_s(char* dest, rsize_t dest_size, const char* src, rsize_t count);
功能:安全版本的 strncpy
,限制复制的字符数并确保目标缓冲区有效性。
规则:
count
需 ≤dest_size - 1
(为终止符预留空间)。- 若
src
长度 ≥count
,复制count
字符并在末尾添加\0
。
示例:
char dest[5];
errno_t err = strncpy_s(dest, sizeof(dest), "Hello World", 4);
// dest 内容为 "Hell\0"
strcat_s
(C11 标准)
原型:
errno_t strcat_s(char* dest, rsize_t dest_size, const char* src);
功能:安全版本的 strcat
,确保拼接后字符串不超过目标缓冲区大小。
规则:
- 剩余空间(
dest_size - strlen(dest) - 1
)必须 ≥strlen(src)
。
示例:
char dest[12] = "Hello"; // 剩余空间为 6
errno_t err = strcat_s(dest, sizeof(dest), " World!");
if (err == 0) { /* 拼接成功 */ }
strnlen
(非 C11,但广泛支持)
原型:
size_t strnlen(const char* s, size_t maxlen);
功能:安全版本的 strlen
,限制最大检查长度以防止未终止字符串导致的越界。
返回值:返回字符串长度(不含 \0
),若未找到 \0
则返回 maxlen
。
示例:
char s[10] = "Hello";
size_t len = strnlen(s, sizeof(s)); // len = 5
3 其他安全替代方案
snprintf
(格式化字符串安全写入)
原型:
int snprintf(char* dest, size_t size, const char* format, ...);
功能:将格式化字符串写入 dest
,最多写入 size-1
个字符,并自动添加 \0
。
返回值:成功时返回欲写入的字符数(不含 \0
),若空间不足则返回所需字符数(可判断是否需要扩容)。
示例:
char dest[10];
int needed = snprintf(dest, sizeof(dest), "Pi=%.2f", 3.14159);
if (needed >= sizeof(dest)) { /* 缓冲区不足 */ }
- 动态内存分配 +
strdup
(需手动释放)
原型:
char* strdup(const char* s); // POSIX 标准
功能:复制字符串到新分配的内存(自动计算长度),避免静态缓冲区溢出风险。
注意:需调用 free()
释放内存。
示例:
const char* src = "Dynamic string";
char* dest = strdup(src);
if (dest != NULL) { /* 使用 dest */free(dest);
}
- 非标准但实用的安全函数
strlcpy
/strlcat
(BSD 扩展)
原型:
size_t strlcpy(char* dest, const char* src, size_t size); // 复制
size_t strlcat(char* dest, const char* src, size_t size); // 拼接
特点:
- 保证目标字符串以
\0
结尾。 - 返回源字符串长度,便于判断是否截断。
- 广泛用于 Linux/BSD,但 Windows 默认不支持。
示例:
char dest[5];
size_t len = strlcpy(dest, "Hello", sizeof(dest)); // len=5, dest="Hell\0"
4 注意事项总结
函数 | 主要风险 | 安全替代方案 |
---|---|---|
strcpy | 缓冲区溢出 | strncpy , snprintf |
strcat | 缓冲区溢出 | strncat |
memcpy | 内存重叠导致未定义行为 | memmove |
gets | 缓冲区溢出(已废弃) | fgets |
- 安全函数的使用原则
- 显式指定缓冲区大小:所有安全版本函数均需传递目标缓冲区大小。
- 检查返回值:正确处理错误码(如
errno_t
或返回的字符数)。 - 避免魔法数值:使用
sizeof(buffer)
而非硬编码长度。 - 编译器支持:需启用 C11 标准(如 GCC 使用
-std=c11
)。 - 跨平台注意:部分安全函数(如
*_s
)在非 Windows 环境可能需额外库支持。
场景 | 危险函数 | 安全替代方案 |
---|---|---|
字符串复制 | strcpy | strcpy_s , snprintf |
字符串拼接 | strcat | strcat_s , strlcat |
内存复制 | memcpy | memcpy_s , memmove |
用户输入读取 | gets | fgets , getline |
未初始化内存操作 | 直接访问指针 | calloc , memset_s |
5 最佳实践
- 优先使用带长度限制的函数(如
strncpy
、strncat
)。 - 检查目标内存大小,避免溢出。
- 处理内存重叠时用
memmove
。 - 动态分配内存后记得释放(如
strdup
)。