C语言安全格式化:snprintf核心指南

snprintf 学习笔记

一、核心定位

  • 归属:C 语言标准库函数(头文件 #include <stdio.h>)
  • 功能:将格式化字符串写入字符缓冲区(char 数组)
  • 核心优势:自带缓冲区越界保护,是 sprintf 的安全升级版(工程开发首选)

二、函数原型与参数

1. 标准原型

cpp 复制代码
int snprintf(char *str, size_t size, const char *format, ...);

2. 参数详解

|--------|--------------|-----------------|---------------------------------|
| 参数 | 类型 | 作用 | 关键注意点 |
| str | char* | 目标缓冲区(存储结果) | 需指向有效可写内存(预计算时可设为 NULL) |
| size | size_t | 缓冲区总字节数 | 必须包含末尾 \0 的占位符(有效字符数 = size-1) |
| format | const char* | 格式控制串(同 printf) | 支持 %s/%d/%f/%.2lf 等占位符 |
| ... | 可变参数 | 待格式化的数据 | 需与 format 占位符一一对应 |

三、核心特性(安全关键)

1. 越界保护机制

  • 当格式化后字符串长度 ≥ size 时:仅写入 size - 1 个有效字符,末尾强制补 \0
  • 彻底避免缓冲区溢出(sprintf 无此特性,易导致程序崩溃)

2. 返回值规则(必记)

  • 返回值 = 格式化后应生成的字符串总长度(不含 \0 (与缓冲区是否装下无关)
  • 判定逻辑:
    • 返回值 < size → 无截断,完整写入
    • 返回值 ≥ size → 有截断,仅写入 size - 1 个字符

四、基础用法(分场景)

场景 1:无截断(正常场景)

cpp 复制代码
char buf[20]; // 缓冲区大小20字节

int ret = snprintf(buf, sizeof(buf), "姓名:%s,年龄:%d", "张三", 20);

// 输出:buf = "姓名:张三,年龄:20",ret = 14(不含\0)

场景 2:有截断(缓冲区不足)

cpp 复制代码
char buf[12]; // 缓冲区仅12字节

int ret = snprintf(buf, sizeof(buf), "姓名:%s,年龄:%d", "张三", 20);

// 输出:buf = "姓名:张三,年"(11个有效字符),ret = 14,buf[11] = '\0'

场景 3:高级用法(动态分配缓冲区)

核心思路:预计算长度 → 动态分配 → 格式化写入
cpp 复制代码
// 1. 预计算所需长度(str=NULL,size=0,不写入仅返回长度)

int need_len = snprintf(NULL, 0, "ID:%d,战力:%ld", 1001, 123456);

// 2. 动态分配缓冲区(+1 留\0位置)

char *buf = (char*)malloc(need_len + 1);

// 3. 正式写入

snprintf(buf, need_len + 1, "ID:%d,战力:%ld", 1001, 123456);

// 4. 用完释放内存

free(buf);

buf = NULL;
优势:无截断、不浪费内存

五、与 sprintf 的核心区别

|---------|----------------|-----------|
| 特性 | snprintf | sprintf |
| 缓冲区溢出保护 | ✅ 自动截断 + 补 \0 | ❌ 无,直接越界 |
| 安全性 | 高(工程首选) | 极低(不推荐使用) |
| 参数要求 | 必须传缓冲区大小 size | 无需传大小 |
| 缓冲区不足行为 | 截断并补 \0 | 覆盖后续内存 |

六、避坑指南(重点)

  1. size 必须传缓冲区总字节数:用 sizeof(buf) 而非 strlen(buf)(strlen 不含 \0)
  2. 返回值不含 \0:判断截断的条件是 ret ≥ size,而非 ret > size
  3. 占位符与参数类型匹配
    1. %d → int,%ld → long,%lld → long long
    2. %f → float,%lf → double,%s → char*
  4. 缓冲区非空(除非预计算):仅预计算长度时可设 str = NULL,其他场景需指向有效内存
  5. 动态分配必做校验:malloc 后需判断 buf != NULL,避免内存分配失败

七、核心要点速记

  1. 安全核心:截断 + 补 \0,杜绝溢出
  2. 关键参数:size 是总字节数(含 \0)
  3. 返回值:应生成的长度(不含 \0),ret≥size 即截断
  4. 工程原则:优先用 snprintf 替代 sprintf
  5. 高级技巧:str = NULL + size = 0 预计算长度,配合 malloc 动态分配

八、实际用例

将九九乘法表保存进数组中,并且从数组中输出,只能用while循环。

cpp 复制代码
//1*1=1
//1*2=2 2*2=4
//1*3=3 2*3=6 3*3=9
//1*4=4 2*4=8 3*4=12 4*4=16
//1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
//1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
//1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
//1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
//1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

#include<stdio.h>
int main()
{
	char shuzu[9][70];
	int i = 1;
	
	while(i <= 9){
		
		shuzu[i - 1][0] = '\0';

		int j = 1;
		
		while(j <= i){
			
			printf("%d*%d=%d\t", i, j, i*j);
			snprintf(shuzu[i - 1], sizeof(shuzu[i - 1]),"%s%d*%d=%d ", shuzu[i - 1], j, i, i*j);
			
			j++;
		}
		printf("\n");
		i++;
	}
	
	printf("\n从数组中打印乘法表:\n\n");
	
	int hang = 0;
	while(hang <= 9){
		printf("%s \n", shuzu[hang]);
		hang++;
	}
	
	return 0;
}

运行结果:

相关推荐
崇山峻岭之间15 小时前
Matlab学习记录18
开发语言·学习·matlab
老王熬夜敲代码16 小时前
C++万能类:any
开发语言·c++·笔记
智者知已应修善业16 小时前
【数组删除重复数据灵活算法可修改保留重复数量】2024-3-4
c语言·c++·经验分享·笔记·算法
你怎么知道我是队长17 小时前
C语言---字符串
java·c语言·算法
你怎么知道我是队长17 小时前
C语言---指针
c语言·数据结构·算法
羊群智妍18 小时前
领跑2026 GEO赛道:SHEEP-GEO登顶十大检测平台,解锁品牌AI可见性最优解
笔记·百度·微信·facebook·新浪微博
QT 小鲜肉18 小时前
【Linux命令大全】002.文件传输之uupick命令(实操篇)
linux·运维·服务器·chrome·笔记
你怎么知道我是队长18 小时前
C语言---函数指针和回调函数
c语言·开发语言
QT 小鲜肉18 小时前
【Linux命令大全】003.文档编辑之colrm命令(实操篇)
linux·运维·服务器·chrome·笔记