15. sscanf 函数详解

头文件

复制代码
#include <stdio.h>

函数原型

复制代码
int sscanf(const char *str, const char *format, ...);

作用:从字符串 str 中,按照 format 格式提取数据,存入后续变量 。

对比:scanf 是从标准输入(键盘)读取;sscanf 是从内存字符串读取。

返回值(非常关键,你代码里漏掉了判断)

返回成功匹配并赋值的变量个数。

  • 成功匹配几项,就返回几;
  • 如果一开始就匹配失败,返回 0;
  • 如果读到字符串结束(EOF),返回 EOF(一般是 -1)。

示例:

复制代码
char buf[] = "GET /index.html HTTP/1.1";
char method[16], path[256], proto[16];
int ret = sscanf(buf, "%s%s%s", method, path, proto);
// 匹配成功3个,ret = 3

常用格式符

格式 说明
%s 读取字符串,遇到空白字符(空格、换行、tab)自动停止 ,不会读取空白;自动追加 \0
%d 读取整数
%f 读取浮点数
%[abc] 读取字符,只接受括号内的字符,遇到不在集合里的字符停止
%[^abc] 读取字符,直到遇到括号里任意字符停止,非常适合按分隔符截取字符串
%*s / %*d * 表示:匹配但是不保存结果,跳过这部分数据

⚠️ %s 巨大坑点:不限制长度,极易缓冲区溢出 !

不安全写法:sscanf(str, "%s", buf);

安全写法:限定最大读取长度(数组大小-1,留位置放\0)

复制代码
char buf[16];
sscanf(str, "%15s", buf); //最多读15个字符,末尾自动补'\0'

几个典型例子

例1:分割HTTP请求行(就是你代码场景)

复制代码
char req[] = "GET /test HTTP/1.1";
char method[16], path[256], proto[16];
int ret = sscanf(req, "%15s %255s %15s", method, path, proto);
if(ret == 3) {
    printf("method=%s, path=%s, proto=%s\n", method, path, proto);
}

输出:method=GET, path=/test, proto=HTTP/1.1

例2:按分隔符截取,%[^] 常用技巧

截取到换行符为止(提取一行)

复制代码
char line[] = "Host: 127.0.0.1\r\n";
char host[128];
sscanf(line, "Host: %[^\r]", host); 
// %[^\r]:一直读,直到碰到 \r 就停止

例3:跳过不需要的字段 %*s

复制代码
char str[] = "GET /index.html HTTP/1.1";
char path[256];
// 跳过第一个字符串,跳过第二个空格,读取path
sscanf(str, "%*s %255s %*s", path);
printf("%s", path); //输出 /index.html

重点注意事项

  1. %s 以空白作为分隔符 ,不能用来读取带空格的字符串;想读带空格的字符串要用 %[^分隔符]

  2. 必须校验返回值,否则非法输入会让变量变成脏数据。你的原始代码没有判断返回值,是严重bug。

  3. 缓冲区溢出 :%s 不加长度限制属于高危漏洞,和 gets() 一样危险。

  4. 参数传递:接收字符串的变量直接传数组名(地址),不要加& ;普通变量如int才需要&。

    char s[16];
    int a;
    sscanf("123 abc", "%d %s", &a, s);

  5. sscanf 只做简单正则风格匹配,能力弱,复杂文本解析不要依赖它。HTTP完整解析不能只靠sscanf。

和你这段HTTP解析代码关联总结

你的代码:

复制代码
sscanf(req, "%s%s%s", hreq->method, hreq->path, hreq->protocol);

优点:一行代码拆分请求行3个字段。

缺点:

  • 没有长度限制,超长输入会内存越界;
  • 没有判断返回值,无法识别非法请求;
  • 只能分割空格分隔的3段,不能解析请求头多行数据。

2. sscanf (req, "% s% s% s", hreq->method, hreq->path, hreq->protocol); 没明白这是怎么解析字符串的,这种只能解析 字符串之间只有一个空白字符(空格、换行、tab)的是吗,遇到一个,解析出来,保存到第一个 % s 里是吗,如果是连续多个空白字符(空格、换行、tab)呢

核心结论先说

%s 的规则:

自动跳过开头所有连续空白(空格 、\t、\n、\r),然后持续读取非空白字符,直到碰到下一个空白就停止,并且自动在末尾加 \0。

所以:

  1. 多个连续空白,和单个空白效果完全一样 ,%s 会一次性把连续空白全部跳过,不会把空白当成内容。
  2. 不是"遇到一个空白就保存当前%s",逻辑顺序是反过来的。

拆解 sscanf(req, "%s%s%s", m, p, proto) 的执行流程

以例子:

复制代码
char req[] = "GET   /index.html   HTTP/1.1";
// 中间是3个空格,连续空白
char method[16], path[256], proto[16];
sscanf(req, "%s%s%s", method, path, proto);

执行步骤:

  1. 处理第1个%s:
    • 指针从开头G开始,前面没有空白,直接读非空白字符 :G E T;
    • 读到后面的空格,停止读取;
    • 把GET存入method,追加\0;
    • 字符串指针停在第一个空格位置。
  2. 处理第2个%s:
    • %s规则:先跳过所有连续空白(这里3个空格全部跳过);
    • 指针跳到/,读取非空白字符直到下一组空格:/index.html;
    • 存入path,追加\0;指针停在后面空格。
  3. 处理第3个%s:
    • 再次跳过全部连续空白;读取HTTP/1.1存入protocol。

✅ 最终结果和只有单个空格:GET /index.html HTTP/1.1 完全相同。

空白字符集合:' '(空格)、\t制表符、\n换行、\r回车。%s全部视为分隔符,多个连续空白等价于一个分隔符。

举个极端例子(混合换行+空格)

复制代码
char req[] = "GET\n\r\t  /demo   HTTP/1.1";

\n\r\t加空格一堆连续空白,sscanf("%s%s%s")照样正常拆出三段。

重点误区澄清

❌ 错误理解:遇到一个空白字符 ,就结束当前%s。

✅ 正确理解:

  1. 每次执行%s:第一步先吃掉所有连续空白;
  2. 第二步读取连续非空白字符;
  3. 直到再次碰到空白字符 ,停止本次%s读取。空白本身不会存入字符串。

空白字符永远不会被%s捕获到结果里。

那什么情况下会解析失败?

只有非空白字符不够3段 的时候,返回值小于3:

例:GET /index 只有两段,sscanf返回2,第三个变量protocol不会被写入。

和HTTP场景关联

HTTP标准请求行:METHOD PATH HTTP/VERSION,规定用空格分隔 ,允许一个或多个空格,所以用%s%s%s刚好可以兼容标准写法。

但是!path里面不能带空格 (HTTP路径本身不允许空格,空格要编码成%20),所以这个写法在解析请求行是可行的Demo方案。

对比:%[^\n] 和 %s 的区别(容易搞混)

  • %s:自动跳过前置空白,读到空白停止,不保留空白
  • %[^\n]:不会跳过前置空白 ,读到\n停止,可以读取包含空格的字符串。

小测试代码,你可以直接复制运行验证

复制代码
#include <stdio.h>
int main()
{
    char req[] = "GET   /index.html   HTTP/1.1";
    char method[16], path[256], proto[16];
    int ret = sscanf(req, "%s%s%s", method, path, proto);
    printf("ret=%d\n", ret);
    printf("method=[%s]\n", method);
    printf("path=[%s]\n", path);
    printf("proto=[%s]\n", proto);
    return 0;
}
相关推荐
C++ 老炮儿的技术栈1 天前
机器人的坐标,像 XYZ 的坐标值,是用单精度浮点还是用双精度浮点?
c语言·c++·人工智能·单片机·c
wflynn2 天前
GitHub 今日推荐|gpu-unlock-ada:Ada 显卡张量算力翻倍解锁
开源·github·c
wflynn3 天前
GitHub 今日推荐|dquickts:能暂停、能存档的 QuickJS
开源·github·c·durable-execution·embeddable·hibernation
real_metrix5 天前
【学习笔记】C/C++ 头文件自包含
c++·c·头文件·自包含
闻缺陷则喜何志丹6 天前
计算24点
c·测试·input·output
2601_955518189 天前
C# 数组 - FindIndex() 方法
c·教程·数组·示例·findindex()
从小白到软工10 天前
3-8 用draw.io来绘制界面*
c
从小白到软工10 天前
3-7 项目-图形版井字棋(下)
c
C++ 老炮儿的技术栈11 天前
char 造成的数据溢出
linux·服务器·c语言·c++·算法·c
从小白到软工12 天前
3-5 画矩形和按钮
c