问题描述
我们知道,最好检查scanf
这样的错误:
if(scanf("%d %d %d",&x,&y,&z) != 3) {
/* Handle error */
}
但是我想知道是否有任何方法可以自动检测它应该是3。
我考虑过的一种方法是分别声明格式字符串,然后解析它。像这样:
const char format[] = "%d %d %d";
size_t n = count(format);
if(scanf(format,&z) != n) {
但是我不知道如何正确实现count
。我可以做一些诸如计算%
的数量的操作,但这很容易出错。如果没有为此提供的库功能,我怀疑将其正确设置将非常困难。
我考虑过的另一种方法是做这样的事情:
void wrapper(const char *format,...) {
va_list arg;
va_start(arg,format);
size_t n = count(arg);
int done = __vfscanf_internal (stdin,format,arg,0);
if(done != n) {
但是据我所知,这里无法编写函数count
。 va_arg
的规范说:“如果ap中没有更多参数时调用va_arg,则行为未定义。” 因此,我无法循环播放直到NULL或类似的东西。 / p>
第三种方法是查看scanf是否支持将此数字写入这样的变量:
int n = scanf("%<somespecifier>%d %d %d",%c,%x,%y,%z);
if(n != c) {
/* Handle error */
}
但这似乎不是一个选择。
所以我在这里茫然。有什么方法可以做我想要的吗?
我的最终目标是写一个“安全”版本的scanf,该版本会在失败时退出。像
void safe_scanf(const char *format,...) {
/* Code */
if(<wrong number of assignments>) {
perror("Wrong number of assignments");
exit(EXIT_FAILURE);
解决方法
您是否考虑过宏?它们支持可变参数的方式有时使它们比常规函数更有用。
下面的代码未经测试,但也许可以帮助您入门。
#define safe_sscanf(fmt,...) {\
const char *p = fmt-1; int n=0;\
while (p=strstr(p+1,"%")) ++n;\
p = fmt-2;\
while (p=strstr(p+2,"%%")) n-=2;\
if (sscanf(fmt,__VA_ARGS__) != std::max(0,n)) exit(1);\
}
,
您可以使用此variadic macro trick来计算传递给函数的参数数量:
#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__,5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
此实现最多支持5个参数,但可以轻松扩展。因此,您可以将scanf
包装到类似如下的包装器中:
#define scanf_checked(...) scanf(__VA_ARGS__) - VA_NUM_ARGS(__VA_ARGS__) + 1
后跟+ 1
,需要从计数中删除格式非可变参数。
假设您传递了正确数量的参数(大多数编译器对此有适当的警告),则必须检查返回值是否为零。此示例使用sscanf_checked
和+ 2
,因为还有一个非可变参数:
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__,...) N
#define scanf_checked(...) scanf(__VA_ARGS__) - VA_NUM_ARGS(__VA_ARGS__) + 1
#define sscanf_checked(...) sscanf(__VA_ARGS__) - VA_NUM_ARGS(__VA_ARGS__) + 2
int main() {
unsigned u1,u2;
int i;
i = sscanf_checked("1 2","%u %u",&u1,&u2);
assert(i == 0);
i = sscanf_checked("1",&u2);
assert(i != 0);
i = sscanf_checked("1",&u1); // note: UB
assert(i == 0); // assert may succeed,but compiler warns for too few arguments
return EXIT_SUCCESS;
}
工作示例here。