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++ 老炮儿的技术栈18 小时前
sizeof操作符
c语言·c++·人工智能·mfc·c
多弗朗皮卡丘3 天前
数据结构番外:环形队列
数据结构·c
91刘仁德3 天前
传输层协议深度拆解:端口寻址、UDP 报文与 TCP 可靠性机制全解析
linux·网络·c++·网络协议·tcp/ip·udp·c
C++ 老炮儿的技术栈4 天前
不一样的数值交换
数据结构·c++·人工智能·算法·c·csdn开发云
C++ 老炮儿的技术栈4 天前
main函数之后的调用
c语言·c++·人工智能·qt·mfc·c
C++ 老炮儿的技术栈6 天前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
haliu6 天前
【FHE 同态加密】我们如何实现同态加密推理(十六):未完成清单与已知边界(同态加密推理的精度、性能与安全)
人工智能·嵌入式·c·fhe·推理引擎·c11·边缘推理·同态加密推理
haliu7 天前
【FHE 同态加密】我们如何实现同态加密推理(十五):实测账本——一跳 1.8 小时,钱花在哪(同态加密推理性能剖析)
人工智能·嵌入式·c·fhe·推理引擎·c11·边缘推理·同态加密推理
haliu8 天前
【FHE 同态加密】我们如何实现同态加密推理(十四):为什么 `RESULT=PASS` 不是判据(纯 C11 · 零依赖)
人工智能·嵌入式·c·fhe·推理引擎·c11·边缘推理·同态加密推理