*scanf 中 %n 运算符的一致性和行为
Consistency and behavior of %n operator in *scanf
我目前正在将一些 HTTP 处理构建到 C 程序中(在 Linux 上使用 glibc 编译),它将位于 nginx 实例后面,并且认为我应该安全地将参数标记化推迟到 sscanf
在这种情况下。
我很高兴地发现从 URI 中提取查询非常简单:
char *path = "/events?a=1&b=2&c=3";
char query[64] = {0};
sscanf(path, "%*[^?]?%64s HTTP", query); // query = "a=1&b=2&c=3"
但我很惊讶事情变得如此之快 i͏̠͚̣̗̲n͓̭̞̹t͈e͉̝̟̘̺r͈e̫st̩̟̠i͏͈͇n͏̠͍g̞͝ :(
int pos = -1;
char arg[32] = {0}, value[32] = {0};
int c = sscanf(query, "%32[^=]=%32[^&]&%n", &arg, &value, &pos);
对于 a=1&b=2
的输入,我得到 arg="a"
、value="1"
、c=2
、pos=4
。完美:我现在可以在 path + pos
上重新运行 sscanf 以获得下一个参数。我为什么会在这里?
嗯,虽然 a=1&
的行为与上述相同,但 a=1
会产生 arg="a"
、value="1"
、c=2
、 和 pos=-1
。我对此有何看法?
为文档争先恐后,我读到了
n Nothing is expected; instead, the number of characters consumed
thus far from the input is stored through the next pointer,
which must be a pointer to int. This is not a conversion and
does not increase the count returned by the function. The as‐
signment can be suppressed with the * assignment-suppression
character, but the effect on the return value is undefined.
Therefore %*n conversions should not be used.
其中超过 50% 的段落提到簿记细节。没有讨论我看到的行为。
在 Google 搜索结果中徘徊,我很快找到了维基百科的 Scanf_format_string 条目(这是最热门的条目),但是,呃...
Oookay... 我觉得我在这里使用一个没人真正关注的功能,就像在风滚草中一样。这并没有激发我剩余的信心。
看一下 where %n
is implemented in vfscanf-internal.c,我发现 60% 的代码(行)涉及标准不一致的讨论,39.6% 是实现细节,0.4% 是实际代码(全部由“done++;
”组成。
*出现* glibc 的行为是保留内部值 done
(我使用 %n
) 未触及 - 或者更确切地说,未定义 - 除非某些操作改变了它。以这种方式使用 %n
似乎也是不可预见的,而且我完全处于“这里是龙”的领域? :(
我不认为我会使用 scanf
...
为了完整起见,这里总结了我所看到的内容。
#include <stdio.h>
void test(const char *str) {
int pos = -1;
char arg[32] = {0}, value[32] = {0};
int c = sscanf(str, "%32[^=]=%32[^&]&%n", (char *)&arg, (char *)&value, &pos);
printf("\"%s\": c=%d arg=\"%s\" value=\"%s\" pos=%d\n", str, c, arg, value, pos);
}
int main() {
test("a=1&b=2"); // "a=1&b=2": c=2 arg="a" value="1" pos=4
test("a=1&"); // "a=1&": c=2 arg="a" value="1" pos=4
test("a=1"); // "a=1": c=2 arg="a" value="1" pos=-1
}
我认为C标准保证你的例子中pos
的值保持不变。
C17 7.21.6.2 说,描述 fscanf
:
(4) The fscanf function executes each directive of the format in turn. When all directives have been
executed, or if a directive fails (as detailed below), the function returns. Failures are described as
input failures (due to the occurrence of an encoding error or the unavailability of input characters),
or matching failures (due to inappropriate input).
[...]
(6) A directive that is an ordinary multibyte character is executed by reading the next characters of the
stream. If any of those characters differ from the ones composing the directive,the directive fails and
the differing and subsequent characters remain unread. Similarly, if end-of-file, an encoding error,
or a read error prevents a character from being read, the directive fails.
(这里的“多字节字符”包括普通的单字节字符比如你的&
.)
因此在您的 "a=1"
示例中,指令 %32[^=]
、=
和 %32[^&]
全部成功,现在已到达字符串的末尾。在 7.21.6.7 中有解释,对于 sscanf
,“到达字符串末尾相当于
遇到 fscanf 函数的文件结尾。”因此无法读取任何字符,因此 &
指令失败,并且 sscanf
returns 没有做任何进一步的事情。 %n
指令从未执行过,因此没有任何事情有权修改 pos
的值。因此它必须具有与之前相同的值,即 -1.
我不认为这种情况是不可预见的;只是它已经被现有规则所涵盖,所以没有人费心去明确地指出它。
我目前正在将一些 HTTP 处理构建到 C 程序中(在 Linux 上使用 glibc 编译),它将位于 nginx 实例后面,并且认为我应该安全地将参数标记化推迟到 sscanf
在这种情况下。
我很高兴地发现从 URI 中提取查询非常简单:
char *path = "/events?a=1&b=2&c=3";
char query[64] = {0};
sscanf(path, "%*[^?]?%64s HTTP", query); // query = "a=1&b=2&c=3"
但我很惊讶事情变得如此之快 i͏̠͚̣̗̲n͓̭̞̹t͈e͉̝̟̘̺r͈e̫st̩̟̠i͏͈͇n͏̠͍g̞͝ :(
int pos = -1;
char arg[32] = {0}, value[32] = {0};
int c = sscanf(query, "%32[^=]=%32[^&]&%n", &arg, &value, &pos);
对于 a=1&b=2
的输入,我得到 arg="a"
、value="1"
、c=2
、pos=4
。完美:我现在可以在 path + pos
上重新运行 sscanf 以获得下一个参数。我为什么会在这里?
嗯,虽然 a=1&
的行为与上述相同,但 a=1
会产生 arg="a"
、value="1"
、c=2
、 和 pos=-1
。我对此有何看法?
为文档争先恐后,我读到了
n Nothing is expected; instead, the number of characters consumed
thus far from the input is stored through the next pointer,
which must be a pointer to int. This is not a conversion and
does not increase the count returned by the function. The as‐
signment can be suppressed with the * assignment-suppression
character, but the effect on the return value is undefined.
Therefore %*n conversions should not be used.
其中超过 50% 的段落提到簿记细节。没有讨论我看到的行为。
在 Google 搜索结果中徘徊,我很快找到了维基百科的 Scanf_format_string 条目(这是最热门的条目),但是,呃...
看一下 where %n
is implemented in vfscanf-internal.c,我发现 60% 的代码(行)涉及标准不一致的讨论,39.6% 是实现细节,0.4% 是实际代码(全部由“done++;
”组成。
*出现* glibc 的行为是保留内部值 done
(我使用 %n
) 未触及 - 或者更确切地说,未定义 - 除非某些操作改变了它。以这种方式使用 %n
似乎也是不可预见的,而且我完全处于“这里是龙”的领域? :(
我不认为我会使用 scanf
...
为了完整起见,这里总结了我所看到的内容。
#include <stdio.h>
void test(const char *str) {
int pos = -1;
char arg[32] = {0}, value[32] = {0};
int c = sscanf(str, "%32[^=]=%32[^&]&%n", (char *)&arg, (char *)&value, &pos);
printf("\"%s\": c=%d arg=\"%s\" value=\"%s\" pos=%d\n", str, c, arg, value, pos);
}
int main() {
test("a=1&b=2"); // "a=1&b=2": c=2 arg="a" value="1" pos=4
test("a=1&"); // "a=1&": c=2 arg="a" value="1" pos=4
test("a=1"); // "a=1": c=2 arg="a" value="1" pos=-1
}
我认为C标准保证你的例子中pos
的值保持不变。
C17 7.21.6.2 说,描述 fscanf
:
(4) The fscanf function executes each directive of the format in turn. When all directives have been executed, or if a directive fails (as detailed below), the function returns. Failures are described as input failures (due to the occurrence of an encoding error or the unavailability of input characters), or matching failures (due to inappropriate input).
[...]
(6) A directive that is an ordinary multibyte character is executed by reading the next characters of the stream. If any of those characters differ from the ones composing the directive,the directive fails and the differing and subsequent characters remain unread. Similarly, if end-of-file, an encoding error, or a read error prevents a character from being read, the directive fails.
(这里的“多字节字符”包括普通的单字节字符比如你的&
.)
因此在您的 "a=1"
示例中,指令 %32[^=]
、=
和 %32[^&]
全部成功,现在已到达字符串的末尾。在 7.21.6.7 中有解释,对于 sscanf
,“到达字符串末尾相当于
遇到 fscanf 函数的文件结尾。”因此无法读取任何字符,因此 &
指令失败,并且 sscanf
returns 没有做任何进一步的事情。 %n
指令从未执行过,因此没有任何事情有权修改 pos
的值。因此它必须具有与之前相同的值,即 -1.
我不认为这种情况是不可预见的;只是它已经被现有规则所涵盖,所以没有人费心去明确地指出它。