C语言中用于提升字符串指针的sscanf包装函数

8

我有一个函数,它会调用一系列的sscanf()函数,然后在每次调用后更新字符串指针,使其指向未被sscanf()消耗的第一个字符,就像这样:

if(sscanf(str, "%d%n", &fooInt, &length) != 1)
{ 
   // error handling
}
str+=length;

为了清理它并避免重复多次,我想将其封装成一个漂亮的实用函数,类似于以下内容:
int newSscanf ( char ** str, const char * format, ...)
{
  int rv;
  int length;
  char buf[MAX_LENGTH];
  va_list args;

  strcpy(buf, format);
  strcat(buf, "%n");
  va_start(args, format);
  rv = vsscanf(*str, buf, args, &length);  //Not valid but this is the spirit
  va_end(args);
  *str += length;

  return rv;
}

那么我可以简化调用如下,以去除额外的参数/簿记:

if(newSscanf(&str, "%d", &fooInt) != 1)
{ 
   // error handling
}

很遗憾,我找不到一种直接或间接在newSscanf()内部将&length参数附加到参数列表末尾的方法。有没有什么办法可以解决这个问题,或者我最好在每次调用时手动处理记录?


你必须放弃使用sscanf()。编写专门的解析器,每个解析器解析特定类型的数据。strtod、strtol和strtok是您的基本工具。 - Hans Passant
3个回答

4

您是正确的 - 无法将额外的参数添加到 va_list。最好的方法可能是像这样使用某些宏技巧:

int _newSscanf ( char ** str, int *length, const char * format, ...)
{
  int rv;
  va_list args;

  va_start(args, format);
  rv = vsscanf(*str, format, args);
  va_end(args);
  *str += *length;

  return rv;
}

#define NEW_SSCANF_INIT int _ss_len
#define newSscanf(str, fmt, ...) _newSscanf(str, &_ss_len, fmt "%n", __VA_ARGS__, &_ss_len)

...并要求调用者执行以下操作:

NEW_SSCANF_INIT;

if (newSscanf(&str, "%d", &fooInt) != 1)
{ 
   // error handling
}

如果您可以使用GCC扩展,可以使用"语句表达式"来省略NEW_SSCANF_INIT部分,使代码更加简洁:

#define newSscanf(str, fmt, ...) ({int _ss_len; _newSscanf(str, &_ss_len, fmt "%n", __VA_ARGS__, &_ss_len);})

除了在_newSscanf中将“buf”替换为“format”,它按原样工作得很好,谢谢! - Dusty
为了永久固定(如果您可以使用GCC扩展,我已经添加了一个改进)。 - caf

1

除非你深入了解可变参数列表的工作原理(从而使你的代码不具备可移植性),否则没有办法修改参数。

但我有一个想法,可能行也可能不行。我没有测试过,因为我真的不认为你应该使用它,但如果你一心想这样做,它可能会有所帮助。

由于你只想获取扫描的字符数,你应该意识到你不必在设置调用者变量的同时进行扫描。

让你的代码扫描字符串以根据调用者的需要设置参数。这里不需要任何更改。

下一步是稍微棘手的。

计算格式字符串中不紧跟着%*%字符的数量——换句话说,需要提供给sscanf的变量数量。如果这个数字大于你的上限,请断言(参见下面的代码)。

然后在你的格式字符串末尾添加%n序列,以确保你将获得字符计数。

然后,使用您的新格式字符串,使用垃圾缓冲区(重复)接收扫描中包括最后一个字符计数在内的所有值。

类似以下内容(调试的责任在您身上):

typedef union {
    char junk[512]; // Be *very* careful with "%s" buffer overflows.
    int length;
} tJunkbuff;

int newSscanf (char **str, const char *format, ...) {
    int rv, length;
    char buf[MAX_LENGTH];
    tJunkBuff junkbuff;
    va_list args;

    // Populate variables.

    va_start (args, format);
    rv = vsscanf (*str, buf, args);
    va_end (args);

    // Get length.

    // String scanning for % count and assert/error left out.
    // Only 20 allowed (depends on number of jb.junk variables below (n-1)).
    strcpy (buf, format);
    strcat (buf, "%n");
    sscanf (*str, buf,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk); // May need to be "&(jb.junk)" ?
    *str += jb.length;

    return rv;
}

如果你决定尝试一下,我很想知道它的进展如何。这是我的工作(和责任),我很高兴向你出售电锯,但如果你在使用时割掉了腿,那就是你自己的问题 :-)

是的,我认为在生产系统中使用这种方法有点太过于hack了,但是对于创造力和滥用联合体的加分 =D - Dusty
在联合体中,它需要是一个实际的char *,指向char[512]缓冲区。无论如何,它都不能可移植地工作 - 想象一下16位int和32位char * - 然后您可以获得不同步,因此它会将"%n"的值写入联合体中的填充。 - caf

0

你正在错误地调用函数,请看一下 char **str 参数,它意味着是一个按引用传递的参数:

if(newSscanf(&str, "%d", &fooInt) != 1)
{ 
   // 错误处理
}

是的,我在那里打错了一个字,现在已经修复了,谢谢。不幸的是,问题仍然存在,我无法向va_list中添加任何内容。 - Dusty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接