【问题标题】:Is there a better way to achieve this result than using sscanf?有没有比使用 sscanf 更好的方法来实现这个结果?
【发布时间】:2019-06-02 08:51:42
【问题描述】:

我需要扫描来自串行流的各种传入消息,以检查它们是否包含此字符串:

“所有内容:已收到:switchX yy(y)”

其中 X = 1 到 9 并且 yy(y) 是“开”或“关”。即“一切:收到:switch4 on”或“一切:收到:switch2 off”等。

我在 ATMega328 上使用以下代码进行检查并将相关变量传递给传输()函数:

valid_data = sscanf(serial_buffer, "Everything: Received: switch%u %s", &switch_number, command);
if(valid_data == 2)
{
  if(strcmp(command, "on") == 0)
    {
       transmit(switch_number, 1);
    }
    if(strcmp(command, "off") == 0)
    {
       transmit(switch_number, 0);
    }
}

当 serial_buffer 输入 ISR 检测到“\n”时触发检查。 '\0' 附加到串行流以终止字符串。

它有效,我并没有受到空间/处理能力的推动,但我只是想知道这是否是实现所需结果的最佳方式?

【问题讨论】:

  • 如果需要,您可以编写一个通用解析器,或者使用strstr 找到正确的偏移量以开始读取。
  • “最佳”的方式是什么指标?讨论各种方法的优缺点很有趣也很有意义,但不太适合 SO 的问答形式。一般来说,我很少推荐使用 scanf 做任何事情,因为它在检测到的分隔符和其他类似的东西方面定义不明确,而是倾向于更喜欢 fgets 并更手动地解析结果字符串,但如果你可以别管它,使用scanf 更紧凑。
  • sscanf 是适合这项工作的工具。您可以通过在字符串末尾添加 \n 来强制执行更严格的输入格式,并通过添加 %4s 来提高安全性,以确保最后一个令牌(命令)不会溢出您的输入缓冲区(它是 4 个字符,因为 @987654330 @ 字符存储在字符串的末尾,由sscanf 自动附加。
  • aside:如果您的第一个条件匹配,请使用else 避免测试另一个。
  • 与此类解析问题一样,如果字符串与格式不匹配,请提供所需的结果 - 或者您是否建议 string 始终格式正确?

标签: c parsing scanf


【解决方案1】:

它有效,我并没有受到空间/处理能力的推动,但我只是想知道这是否是实现所需结果的最佳方式?

目前尚不清楚您希望我们判断哪些标准,因为速度和内存使用都不是一个紧迫的问题,但在没有这些考虑的压力的情况下,我个人认为代码的简单性和清晰性是源代码最重要的标准,当然,除了正确性。

从这个角度来看,基于sscanf() 的解决方案很好,尤其是使用相对简单的格式字符串,例如您实际上拥有的格式字符串。您要匹配的行的模式在格式字符串中非常清晰,并且以下逻辑也清晰简单。作为奖励,它还应该生成小代码,因为库函数完成了大部分工作,并且有理由希望实现已经为优化该函数付出了一些努力以获得良好的性能,所以即使在那些方面也可能是一个胜利您不太关心的标准。

但是,存在一些可能的正确性问题:

  • sscanf() 与空格字面不匹配。格式字符串中的一个或多个空白字符与输入中的任何零个或多个空白字符匹配。
  • sscanf() 在大多数字段之前跳过前导空格,尤其是在 %u 字段之前。
  • 可以扫描指定范围 1 - 9 之外的开关号。
  • 命令缓冲区很容易溢出。
  • sscanf() 将在最后一个字段匹配后忽略输入字符串中的任何内容

如果需要,所有这些问题都可以处理。例如,这里有一个替代方案,可以处理除单词之间的空白数量之外的所有其他问题(但包括避免“开关”和数字之间的空白):

unsigned char switch_number;
int nchars = 0;

int valid_data = sscanf(serial_buffer, "Everything: Received: switch%c %n",
        &switch_number, &nchars);

if (valid_data >= 1 && switch_number - (unsigned) '1' < 9) {
    char *command = serial_buffer + nchars;

    if (strcmp(command, "on") == 0) {
        transmit(switch_number - '0', 1);
    } else if (strcmp(command, "off") == 0) {
        transmit(switch_number - '0', 0);
    }  // else not a match
} // else not a match

与您的主要区别包括

  • 通过%c 指令读取开关号,该指令读取单个字符而不跳过前导空格。验证条件switch_number - (unsigned) '1' &lt; 9 确保读取的字符介于“1”和“9”之间。它利用了无符号算术环绕这一事实。

  • 不是将命令读入单独的缓冲区,而是通过%n 指令捕获前导子字符串的长度。这允许针对“on”和“off”测试整个尾部,从而无需额外的缓冲区,并使您能够拒绝带有尾随单词的行。

  • 如果您也想检查所有空格是否完全匹配,那么%n 也可以提供帮助。例如,

    if (nchars == 30 && serial_buffer[11] == ' ' && serial_buffer[21] == ' '
            serial_buffer[29] == ' ') // it's OK
    

【讨论】:

  • 非常有帮助。感谢您的回复。
猜你喜欢
  • 2011-10-23
  • 1970-01-01
  • 2021-11-15
  • 2020-04-05
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 2012-01-11
  • 2013-08-04
相关资源
最近更新 更多