【软件安全】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'。
相关推荐
Felven8 小时前
stress-ng 与 fio 性能测试工具使用指南
linux·测试工具·fio·stress-ng
huainingning9 小时前
RJ SW Console口忘记密码处理方法
linux·运维·服务器
布裘9 小时前
【银河麒麟】桌面系统循环登录,无法进入桌面?
linux·运维·服务器
慧都小项9 小时前
程序到了Linux才出错?用CLion把调试接到目标环境
linux·运维·服务器
汤姆yu10 小时前
Gemini 4 Argon模型综述:技术、能力与实际使用指南
网络·安全·web安全·ai·大模型
hasty10 小时前
policy.xml 在,不代表策略生效:ImageMagick 补丁给安全配置验收的提醒
xml·mysql·安全
Ruiery11 小时前
Linux 6.6内核内存管理深度解析(一):物理内存初始化 — memblock 怎么把内存交给 buddy
linux·运维·服务器
2401_8685347811 小时前
无线网络规划设计
服务器·网络
库拉镜像AI牛牛12 小时前
短剧内容自动化生产:知漫剧工作室落地教程
大数据·服务器·前端·人工智能·语音识别
天蓝蓝的本我12 小时前
conda日常用到的命令积累1-登录linux,安装pycharm
linux·pycharm·conda