头文件
#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重点注意事项
%s以空白作为分隔符,不能用来读取带空格的字符串;想读带空格的字符串要用%[^分隔符]- 必须校验返回值,否则非法输入会让变量变成脏数据。你的原始代码没有判断返回值,是严重bug。
- 缓冲区溢出:
%s不加长度限制属于高危漏洞,和gets()一样危险。 - 参数传递:接收字符串的变量直接传数组名(地址),不要加
&;普通变量如int才需要&。
char s[16]; int a; sscanf("123 abc", "%d %s", &a, s);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。
所以:
- 多个连续空白,和单个空白效果完全一样,
%s会一次性把连续空白全部跳过,不会把空白当成内容。 - 不是“遇到一个空白就保存当前%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个
%s:- 指针从开头
G开始,前面没有空白,直接读非空白字符:G E T; - 读到后面的空格,停止读取;
- 把
GET存入method,追加\0; - 字符串指针停在第一个空格位置。
- 指针从开头
- 处理第2个
%s:%s规则:先跳过所有连续空白(这里3个空格全部跳过);- 指针跳到
/,读取非空白字符直到下一组空格:/index.html; - 存入
path,追加\0;指针停在后面空格。
- 处理第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。
✅ 正确理解:
- 每次执行
%s:第一步先吃掉所有连续空白;- 第二步读取连续非空白字符;
- 直到再次碰到空白字符,停止本次
%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; }