这是一个非常基础但又极其重要且容易出错的函数,很多初学者(甚至一些有经验的开发者)都会对它的行为感到困惑。

(图片来源网络,侵删)
函数简介
strncpy 是 C 标准库 <string.h> 中的一个函数,用于从源字符串复制指定数量的字符到目标字符串中。
函数原型:
char *strncpy(char *dest, const char *src, size_t n);
参数:
dest: 目标字符数组(或指向字符的指针),用于存储复制后的字符串。src: 源字符串(常量字符指针),即要被复制的字符串。n: 要复制的最大字符数(size_t类型)。
返回值:

(图片来源网络,侵删)
- 返回目标字符串
dest的起始地址。
strncpy 的工作原理(核心与陷阱)
strncpy 的行为可以分为两种主要情况,理解这两种情况是正确使用它的关键。
源字符串长度小于 n
如果源字符串 src 的长度(不包括结尾的 \0)小于 n:
strncpy会将src的所有字符(包括\0)复制到dest中。- 它会在
dest的剩余空间中用\0填充,直到总共复制了n个字符为止。
示例:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
char dest[10] = {0}; // 初始化为全0,方便观察
printf("Before copy: dest = \"%s\"\n", dest);
// src长度为5,n为10
strncpy(dest, src, 10);
printf("After copy: dest = \"%s\"\n", dest);
// 输出: After copy: dest = "Hello"
// 注意:dest的后面5个字节都是'\0'
printf("dest[5] is: %c (value: %d)\n", dest[5], dest[5]); // 输出空字符,值为0
return 0;
}
在这个例子中,"Hello" 只有5个字符。strncpy 会复制 'H', 'e', 'l', 'l', 'o', '\0',然后用4个额外的 \0 填满 n=10 的要求。dest 最终变成 "Hello\0\0\0\0\0"。

(图片来源网络,侵删)
源字符串长度大于或等于 n
如果源字符串 src 的长度大于或等于 n:
strncpy会从src中复制 最多n个字符 到dest中。- 它不会自动在
dest的末尾添加\0(空字符)!
这是 strncpy 最常见的陷阱! 如果你不手动处理,dest 就不是一个合法的以 \0 结尾的 C 字符串,后续任何试图用 printf("%s", dest) 或 strlen(dest) 等函数操作它的行为都可能导致 未定义行为,通常是缓冲区越界读取,程序崩溃或输出乱码。
示例:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "This is a very long string";
char dest[10] = {0}; // 目标缓冲区大小为10
printf("Before copy: dest = \"%s\"\n", dest);
// src长度远大于10,n为10
strncpy(dest, src, 10);
printf("After copy: dest = \"%s\"\n", dest); // 危险!dest没有'\0'
// 输出可能是: "This is a ",但也可能因为越界而崩溃或输出乱码
// 安全的做法是手动添加终止符
dest[9] = '\0'; // 强制在复制的最后一个字符后添加'\0'
printf("After manual null-terminator: dest = \"%s\"\n", dest); // 输出: "This is a"
return 0;
}
与 strcpy 的对比
| 特性 | strcpy(dest, src) |
strncpy(dest, src, n) |
|---|---|---|
| 功能 | 复制整个源字符串 | 最多复制 n 个字符 |
终止符 \0 |
总是在目标末尾添加 | 不一定添加,取决于 src 长度和 n 的关系 |
| 安全性 | 不安全,容易导致缓冲区溢出 | 相对安全,因为它限制了复制的最大数量,但程序员需要手动处理 \0 |
| 效率 | 通常更快 | n 很大而 src 很短,会因为填充 \0 而变慢 |
安全的 strncpy 使用模式
为了安全地使用 strncpy,你应该养成以下习惯:
- 确保目标缓冲区足够大:
dest的大小至少为n + 1,以容纳n个字符和一个\0。 - 总是手动添加终止符:尤其是在你怀疑源字符串可能比
n长的情况下,复制完成后,手动将dest[n-1]设置为\0,这是一种防御性编程。
// 安全的使用模式 char src[] = "dangerous string"; char dest[20]; // 缓冲区足够大 // 复制最多 19 个字符,为 '\0' 留出空间 strncpy(dest, src, 19); // 手动确保字符串终止 dest[19] = '\0';
现代 C 的替代方案:strncpy_s (C11 标准)
由于 strncpy 的这些缺陷,C11 标准引入了一个更安全的版本:strncpy_s。
函数原型:
errno_t strncpy_s(char *dest, rsize_t dest_size, const char *src, rsize_t n);
优点:
- 显式指定目标大小:
dest_size是目标缓冲区的总大小,这比strncpy的n更不容易出错。 - 强制终止符:
strncpy_s保证结果字符串总是以\0- 错误检查:如果发生缓冲区溢出(
n大于dest_size-1),strncpy_s会执行一个运行时约束违规,通常会导致程序立即终止,而不是产生未定义行为,这使得错误更容易被发现。 - 错误检查:如果发生缓冲区溢出(
示例:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "This is a very long string";
char dest[10];
// 安全地复制最多 9 个字符 (为 '\0' 留出空间)
// n (20) > dest_size (10) - 1,程序会在这里终止并报错
// strncpy_s(dest, sizeof(dest), src, 9); // 这是安全的
// 尝试一个不安全的调用
// strncpy_s(dest, sizeof(dest), src, 20); // 这会触发运行时约束违规
// printf("This line will not be reached.\n");
printf("Copy successful: %s\n", dest);
return 0;
}
注意:strncpy_s 是 C11 标准的一部分,如果你的编译器(如 GCC)在默认情况下不完全支持 C11,你可能需要添加编译选项(如 -std=c11)来使用它,在 Visual Studio 中,它默认可用。
| 函数 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
strcpy |
简单、直接 | 极不安全,极易导致缓冲区溢出 | 不推荐 |
strncpy |
限制了最大复制长度 | 不保证终止符,行为复杂,容易误用 | 可用,但必须非常小心 |
strncpy_s |
安全,强制终止,有运行时检查 | C11 标准,兼容性稍差 | 强烈推荐(如果环境支持) |
- 在学习和理解底层原理时,
strncpy是一个必须掌握的函数。 - 在现代 C 项目中,优先使用
strncpy_s,因为它更安全,能避免很多难以追踪的 bug。 - 如果必须使用
strncpy,请务必 手动添加终止符 并确保目标缓冲区足够大。
