这是一个非常常用但又容易用错的函数,理解它的行为至关重要。

(图片来源网络,侵删)
函数原型
strncpy 函数在 <string.h> 头文件中声明,其原型如下:
char *strncpy(char *dest, const char *src, size_t n);
功能描述
strncpy 的主要目的是将源字符串 src 的内容复制到目标缓冲区 dest 中,但它与 strcpy 有一个关键区别:strncpy 允许你指定要复制的最大字符数 n。
参数详解
dest: 目标缓冲区,这是一个字符数组(或指向字符数组的指针),用于存储复制过来的字符串。必须确保这个缓冲区足够大,至少为n个字符,否则会导致缓冲区溢出,这是非常危险的。src: 源字符串,这是一个以'\0'(空字符) 结尾的字符数组。strncpy会从src的第一个字符开始复制。n: 要复制的最大字符数,这是一个size_t类型的无符号整数。
返回值
- 成功时,返回指向目标缓冲区
dest的指针。
核心行为(非常重要!)
strncpy 的行为是 strncpy 最容易让人困惑的地方,因为它有两种截然不同的情况,取决于源字符串 src 的长度是否小于 n。
源字符串长度 < n (strlen(src) < n)
如果源字符串的长度(不包括结尾的 '\0')小于 n,strncpy 会执行以下操作:

(图片来源网络,侵删)
- 将
src中的所有字符(包括结尾的'\0')复制到dest中。 - 在
dest中已复制的'\0'之后,用额外的'\0'字符填充,直到总共复制了n个字符为止。
示例:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello"; // strlen(src) = 5
char dest[10]; // 目标缓冲区足够大
strncpy(dest, src, 7); // n=7, 大于 strlen(src)
printf("dest: \"%s\"\n", dest); // 输出: "Hello"
printf("dest content (as raw bytes): ");
for (int i = 0; i < 7; i++) {
// 使用 %c 打印,非打印字符用 '.' 代替
printf("%c", dest[i] >= 32 && dest[i] <= 126 ? dest[i] : '.');
}
printf("\n"); // 输出: Hello.....
return 0;
}
分析:
src的内容是'H', 'e', 'l', 'l', 'o', '\0'。strncpy复制了这 6 个字符(5个字母 + 1个'\0')。- 因为
n=7,还需要再复制 1 个字符。strncpy会在dest的第 6 个位置再写入一个'\0'。 dest的前 7 个字节是:'H', 'e', 'l', 'l', 'o', '\0', '\0'。- 当
printf用%s打印dest时,它遇到第一个'\0'就停止了,所以输出是 "Hello"。
源字符串长度 >= n (strlen(src) >= n)
如果源字符串的长度大于或等于 n,strncpy 会执行以下操作:
- 从
src中最多复制n个字符到dest中。 - 关键点:它不会自动在
dest的末尾添加'\0'(空字符)!
示例:

(图片来源网络,侵删)
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!"; // strlen(src) = 13
char dest[10];
strncpy(dest, src, 7); // n=7, 小于 strlen(src)
printf("dest: \"%s\"\n", dest); // 输出行为是未定义的!
printf("dest content (as raw bytes): ");
for (int i = 0; i < 10; i++) {
printf("%c", dest[i] >= 32 && dest[i] <= 126 ? dest[i] : '.');
}
printf("\n"); // 输出: Hello,..
return 0;
}
分析:
strncpy从src复制了前 7 个字符:'H', 'e', 'l', 'l', 'o', ',', ' '(空格)。- 因为
n=7,复制操作停止。 dest的前 7 个字节是:'H', 'e', 'l', 'l', 'o', ',', ' '。- 注意:这里没有
'\0'! - 当
printf用%s打印dest时,它会从第一个字符开始打印,直到它在内存中随机遇到一个'\0'字符为止,这会导致输出不可预测,甚至可能越界访问内存,引发程序崩溃,这就是为什么printf(dest)的行为是“未定义的”。
如何安全地使用 strncpy
正是因为 strncpy 在第二种情况下不添加 '\0' 的特性,使得它变得“不安全”且容易误用,以下是几种推荐的、更安全的用法模式。
手动添加终止符(最常见)
如果你只是想复制一个字符串,并且希望 dest 是一个合法的以 '\0' 结尾的字符串,你应该总是手动添加终止符。
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "This is a very long string";
char dest[20];
// 1. 复制
strncpy(dest, src, 19); // n = dest缓冲区大小 - 1
// 2. 手动添加终止符
dest[19] = '\0';
printf("dest: \"%s\"\n", dest); // 输出: "This is a very long"
// 注意:字符串被截断了,但它是安全的
return 0;
}
优点:
- 简单直观。
- 确保
dest总是合法的 C 字符串。
缺点:
src比n短,strncpy会用多余的'\0'填充,这有点浪费。
先复制,再截断(更高效)
这种方法避免了 strncpy 的填充行为,效率更高。
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Short";
char dest[20];
// 1. 使用 strcpy 复制,因为它更快
strcpy(dest, src);
// 2. 如果需要,手动截断并添加终止符
// 假设我们只想保留前 3 个字符
size_t max_len = 3;
dest[max_len] = '\0';
printf("dest: \"%s\"\n", dest); // 输出: "Sho"
return 0;
}
优点:
- 效率高,没有不必要的填充。
- 逻辑清晰。
缺点:
- 代码行数稍多。
使用更现代、更安全的替代品(强烈推荐)
现代 C 标准库提供了一些更安全的替代函数,它们的行为更符合直觉。
-
strlcpy(非标准,但广泛可用)strlcpy由 OpenBSD 引入,在很多系统(如 BSD, macOS, Linux 上的某些版本)中都可以找到,它的行为是:- 总是保证
dest以'\0'- 返回的是
src的总长度,而不是dest的指针,这使得调用者可以轻松判断是否发生了截断。 - 返回的是
#include <stdio.h> // 注意:需要包含 strlcpy 的头文件,通常是 <bsd/string.h> // 在 Linux 上可能需要安装 libbsd 开发包 // 模拟 strlcpy 的行为来演示 size_t my_strlcpy(char *dest, const char *src, size_t size) { size_t src_len = strlen(src); if (size > 0) { size_t copy_len = (src_len < size - 1) ? src_len : size - 1; memcpy(dest, src, copy_len); dest[copy_len] = '\0'; } return src_len; } int main() { char src[] = "A much longer source string"; char dest[10]; size_t total_length = my_strlcpy(dest, src, sizeof(dest)); printf("dest: \"%s\"\n", dest); // 输出: "A much l" printf("Source string length was: %zu\n", total_length); if (total_length >= sizeof(dest)) { printf("Warning: Truncation occurred!\n"); } return 0; } - 总是保证
-
snprintf(C99 标准,最推荐)snprintf是一个格式化输出函数,但用它来复制字符串是极其安全和灵活的,它会确保目标缓冲区总是以'\0'并且不会发生溢出。#include <stdio.h> int main() { char src[] = "Another safe way to copy"; char dest[15]; // 使用 snprintf 复制字符串 // %s 表示字符串,sizeof(dest) 是最大写入的字节数(包括 '\0') snprintf(dest, sizeof(dest), "%s", src); printf("dest: \"%s\"\n", dest); // 输出: "Another safe " // 注意:被安全截断了 return 0; }snprintf的优点:- 标准 C99,任何现代编译器都支持。
- 绝对安全,保证
dest以'\0'- 功能强大,除了复制,还可以进行格式化。
- 返回值是(如果不受限制)将要写入的字符数,可用于判断是否截断。
| 特性 | strcpy |
strncpy (不手动处理) |
strncpy (手动处理) |
strlcpy / snprintf |
|---|---|---|---|---|
| 安全性 | 低,易溢出 | 低,可能不添加 '\0' |
高 | 非常高 |
| 行为 | 简单复制 | 复制 n 个,可能不 '\0'
| ||
| 填充 | 无 | src短时用 '\0' 填充 |
无 | 无 |
| 推荐度 | 不推荐 | 不推荐 | 可接受 | 强烈推荐 |
核心建议:
- 避免直接使用
strncpy,除非你完全理解并记得处理其不添加终止符的“陷阱”。 - 在现代 C 编程中,优先使用
snprintf来进行字符串复制和拼接,它安全、灵活且是标准的一部分。 - 如果你的平台支持
strlcpy并且你确信代码的可移植性要求不包含不支持它的系统(如 Windows),strlcpy也是一个极佳的选择。
