【软件安全】fgets / strncpy / gets(不安全) / snprintf的对比

fgets / strncpy / gets / snprintf 做一个系统对比

gets 禁用 历史罪人

fgets 输入首选

strcpy = 无上限写 → 典型溢出源(更糟)。

strncpy = 有上限但可能不终止 → 容易踩坑,不是"安全 strcpy"。

snprintf:格式化输出首选

总览速查表

函数 典型签名 作用 上界控制 是否保证以 '\0' 结尾 常见坑点 结论
fgets char *fgets(char *s, int n, FILE *fp) 从流(如 stdin)读入一行 :最多读 n-1 个字节 保证 (若 n>0 且成功读到) 可能把换行符也读进来;EOF/错误时返回 NULL 首选的行输入;读完可"去掉换行"
strncpy char *strncpy(char *dst, const char *src, size_t n) 拷贝最多 n 个字符 :最多拷 n 不保证 (当 src 长度 ≥ n'\0' 易产生无终止静默截断 ;短源会用 '\0' 填充剩余部分(低效) 不推荐当作安全版 strcpy ;仅在定长记录场景可用
gets char *gets(char *s) stdin 读一行 :完全无上界 不适用 经典缓冲区溢出通道;已在 C11 移除 禁止使用
snprintf int snprintf(char *s, size_t n, const char *fmt, ...) 格式化输出到缓冲区 :最多写入 n-1 个可见字符 保证 (C99+ 当 n>0 截断会发生;须检查返回值(需要的长度) 首选的格式化写入;可用返回值做容量检查/重分配

1) fgets ------ 读取字符串的首选输入 API

如何工作

  • 最多读取 n-1 个字符,遇到换行EOF 停止,把读到的内容放进 s,并写入终止 '\0'
  • 若缓冲区足够大且读到换行,换行也会留在字符串里 (常见"尾部多一个 \n"的问题)。

返回值

  • 成功:返回 s;失败(EOF/错误且没有读取到字符):返回 NULL

常见坑

  • 忘了去掉尾部 \n 导致比较/打印时异常。
  • n 写成缓冲区大小以外的值。

推荐用法(去掉尾部换行)

c 复制代码
char buf[256];
if (fgets(buf, sizeof buf, stdin)) {
    // 去掉尾部换行
    buf[strcspn(buf, "\n")] = '\0';
    // 使用 buf ...
}

适用场景

  • stdin/文件读入文本行,有上界且自动 NUL 终止,最安全易用。

2) strncpy ------ 不是"安全 strcpy",更像"定长区域拷贝"

如何工作

  • src 拷贝最多 n 个字符到 dst

    • src 长度 < n'\0' 填满剩余(零填充行为)。
    • src 长度 ≥ n不会写入终止 '\0'(这点最危险)。

返回值

  • 返回 dst 指针。

常见坑

  • src 较长时,dst '\0' 终止 ,随后对 dst 做字符串操作(如 printf("%s", dst) / strlen(dst))会越界读取
  • 被误当成"有界且总是安全"的复制函数;而零填充还会造成无谓的性能损耗

安全使用(若非用不可)

c 复制代码
char dst[16];
strncpy(dst, src, sizeof dst - 1);
dst[sizeof dst - 1] = '\0';  // 手动确保终止

更好的替代

  • POSIX:strlcpy(dst, src, sizeof dst)(非标准,但常见于 BSD/部分 Linux 发行版)。
  • 纯标准场景:已知长度时用 memcpy + 手动 '\0';或直接用 snprintf

适用场景

  • 固定长度字段 (如结构体里的定长数组,二进制记录),需要零填充才能对齐/复用的情况。不适合一般字符串复制

3) gets ------ 历史罪人,已被删除

问题本质

  • 不进行边界检查,读入直到换行/EOF,极易缓冲区溢出
  • C11 起已从标准库移除;大多数现代编译器会报错或强烈警告。

替代

  • fgets(buf, sizeof buf, stdin)

结论

  • 永远不要使用

4) snprintf ------ 安全的格式化输出首选

如何工作

  • 把格式化后的字符串写入 s最多写 n-1 个字符 并自动补 '\0'C99+ 前提:n>0)。

  • 返回值 :欲写入的总长度(不含终止符)。

    • 若返回值 >= n,说明发生了截断 ;可据此扩容并重试

示例:一次写入,检查截断

c 复制代码
char buf[32];
int need = snprintf(buf, sizeof buf, "user=%s id=%d", user, id);
if (need < 0) {
    // 格式化失败(很少见)
} else if ((size_t)need >= sizeof buf) {
    // 截断发生:need 是所需大小(不含 '\0')
    // 可选择:分配 need+1 大小重试
}

避免的坑

  • 忽视返回值,导致静默截断、关键信息丢失。
  • 传错 n(应当是缓冲区真实大小)。

适用场景

  • 一切"打印到字符串"的需求;替代 sprintf / vsprintf

该选谁?(实践建议)

  • 读文本行 → 用 fgets,随后按需去掉换行
  • 格式化拼接 → 用 snprintf,并检查返回值是否截断。
  • 二进制/定长记录拷贝 → 特殊场景可用 strncpy(随后手动终止或只作定长字段拷贝,不当作 C 字符串)。
  • 禁止gets(已移除,安全性极差)。

常见安全替代 & 小技巧

  • 去换行buf[strcspn(buf, "\n")] = '\0';
  • 拼路径/键值 :统一用 snprintf,避免 strcat/strcpy
  • 要"安全复制字符串" :优先 snprintf(dst, n, "%s", src)strlcpy(若可用)。
  • 读取整行且不丢字符fgets 循环读取直到遇到 \n,或使用 POSIX getline(自动扩容,非 C 标准)。

迷你示例:综合使用

c 复制代码
// 从 stdin 读一行用户名,然后安全拼接日志前缀
char user[64];

if (!fgets(user, sizeof user, stdin)) {
    fprintf(stderr, "input error\n");
    return 1;
}
user[strcspn(user, "\n")] = '\0';  // 去掉尾部换行

char line[128];
int need = snprintf(line, sizeof line, "[login] user=%s", user);
if (need < 0) {
    fprintf(stderr, "format error\n");
    return 1;
}
if ((size_t)need >= sizeof line) {
    fprintf(stderr, "truncated: needed %d bytes\n", need + 1);
    // 可选择:动态分配 need+1 继续写
}

puts(line);

小结

  • gets禁用fgets读输入首选snprintf格式化输出首选 (记得看返回值);strncpy不是安全版 strcpy ,除定长记录 外尽量避免,并手动补 '\0'
相关推荐
树℡独6 小时前
ns-3仿真之应用层(五)
服务器·网络·tcp/ip·ns3
嵩山小老虎7 小时前
Windows 10/11 安装 WSL2 并配置 VSCode 开发环境(C 语言 / Linux API 适用)
linux·windows·vscode
Fleshy数模7 小时前
CentOS7 安装配置 MySQL5.7 完整教程(本地虚拟机学习版)
linux·mysql·centos
a41324477 小时前
ubuntu 25 安装vllm
linux·服务器·ubuntu·vllm
Configure-Handler8 小时前
buildroot System configuration
java·服务器·数据库
津津有味道8 小时前
易语言TCP服务端接收刷卡数据并向客户端读卡器发送指令
服务器·网络协议·tcp·易语言
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.9 小时前
Keepalived VIP迁移邮件告警配置指南
运维·服务器·笔记
Genie cloud9 小时前
1Panel SSL证书申请完整教程
服务器·网络协议·云计算·ssl
一只自律的鸡9 小时前
【Linux驱动】bug处理 ens33找不到IP
linux·运维·bug
17(无规则自律)9 小时前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考