【问题标题】:lack of understanding about sscanf usage对 sscanf 的使用缺乏了解
【发布时间】:2016-08-20 04:47:01
【问题描述】:

我想解析一个特定的行。因此,我编写了以下代码来测试逻辑,但我可能理解错误:

typedef struct vers
{
   char tu8UVersion[5];
   char tu8UCommit[32];
}tst_prg_versions;

int main(int argc, char **argv)
{
    tst_prg_versions lstVer;
    char buf1[32];
    char buf2[32];

    char str[] = "BOARD-VERS-v1.0.0-git+9abc12345a";
    sscanf(str, "BOARD-VERS-v%5s-git+%s", lstVer.tu8UVersion, lstVer.tu8UCommit);
    printf("vers='%s'\n", lstVer.tu8UVersion);
    printf("commit='%s'\n", lstVer.tu8UCommit);

    sscanf(str, "BOARD-VERS-v%5s-git+%s", buf1, buf2);
    printf("vers='%s'\n", buf1);
    printf("commit='%s'\n", buf2);
    return 0;
}

一旦执行,它就会返回:

vers='1.0.09abc12345a'
commit='9abc12345a'
vers='1.0.0'
commit='9abc12345a

为什么第一个 vers 等于 1.0.09abc12345a 而不是 1.0.0

【问题讨论】:

  • @user3121023 感谢您的回答!为了避免这种问题,将结构变量声明为 char * 更好吗? sscanf 负责内存分配
  • scanf 不会为你分配内存;您必须提供足够大小的缓冲区。
  • printf("vers='%s'\n", lstVer.tu8UVersion); 不仅是错误的,而且是未定义的行为。
  • @MOehm 根据这​​个话题stackoverflow.com/questions/15414022/…scanf 是可以分配内存的吧?
  • 是的,但这似乎是一个 GNU 扩展,因此不可移植。在堆上为五个字母的字符串分配空间也不是一个好主意,以便以后必须释放它。更好的方法是使缓冲区稍大,例如[10],然后使用%9[^-] 扫描不是连字符的所有内容。这将使您能够扫描具有两位数的版本。 (字符串格式%s 将所有内容扫描到下一个空格。或者,将数字扫描为数字。您还应该测试sscanf 的返回值。)

标签: c parsing scanf


【解决方案1】:

为什么第一个版本等于 1.0.09abc12345a 而不是 1.0.0?

记住你有

typedef struct vers
{
   char tu8UVersion[5];
   char tu8UCommit[32];
}tst_prg_versions;

我猜,tu8UVersiontu8UCommit 的内存很有可能是连续的。因为你有 not null-terminated tu8UVersion 当你这样做时:

printf("vers='%s'\n", lstVer.tu8UVersion);

它继续打印 tu8UCommit 并停止,因为 tu8UCommit 以空值终止。

虽然 sscanf 似乎是最明智的解决方案,但您也可以引入一些格式:

char tu8UVersion[32];
   /*  version number can't get too big.
    *  So the first step is do allocated a
    *  reasonably - but not too - big size for it.
    *  So that you can be sure there are few empty bytes at the end.
    */

然后使用函数清理字符串:

char* sanitized(char* ptr)
{
  if(ptr[strlen(ptr)]!='\0')  // include string.h for strlen
     ptr[strlen(ptr)]='\0';
  return ptr;
}

并像这样打印它:

 printf("vers='%s'\n", sanitized(lstVer.tu8UVersion));

【讨论】:

    【解决方案2】:

    您的问题已在 cmets 中确定:您没有为终止的空字符留出空间,并且两个字符串一起运行。

    如果您想扫描您事先不知道大小的版本,您可以使用%[.-9] 将要扫描的字符限制为十进制数字和点,或者使用%[^-] 将扫描的字符限制为除连字符之外的所有字符。 (%[...] 格式类似于%s,不同之处在于您必须在括号中提供有效字符的列表。插入符号作为第一个字母意味着该字符串由未列出的字符组成。换句话说,@ 987654325@是%[^ \t\n]的缩写

    扫描字符串时,应测试sscanf 的返回值,以确保所有项目都已正确扫描并包含有效值。

    这是一个可以扫描多达 11 个字母的版本号的变体:

    #include <stdlib.h>
    #include <stdio.h>
    
    typedef struct vers
    {
       char tu8UVersion[12];
       char tu8UCommit[32];
    } tst_prg_versions;
    
    int main(int argc, char **argv)
    {
        tst_prg_versions lstVer;
    
        char str[] = "BOARD-VERS-v1.0.0-git+9abc12345a";
        int n;
    
        n = sscanf(str, "BOARD-VERS-v%11[^-]-git+%s",
            lstVer.tu8UVersion, lstVer.tu8UCommit);
    
        if (n == 2) {
            printf("vers='%s'\n", lstVer.tu8UVersion);
            printf("commit='%s'\n", lstVer.tu8UCommit);
        } else {
            puts("Parse error.");
        }
    
        return 0;
    }
    

    【讨论】:

    • 感谢您的回答!!我以后一定会再次使用这些信息
    【解决方案3】:

    第一个实际上是 1.0.0!但是,问题是 tu8UVersion 不是以空值终止的,因此 printf(不是 sscanf)在字段之外打印(但是,这样做是未定义的行为,正如 sjsam 所指出的那样)-紧随其后的是 tu8UCommit(不一定必须是这样,由于对齐原因,中间可能还有一些填充字节!)。

    您需要最多打印 5 个字符(%.5s 在 printf 格式字符串中)或留下以 0 终止 tu8UVersion 的位置,正如评论中所建议的那样。

    您的缓冲区也可能发生类似的情况。您很幸运,它们似乎已经被初始化为 0(可能是因为编译为调试版本),这也不一定要发生。因此,如果运气不好,您可能已经打印了 buf1 的整个其余部分(被留在垃圾中)甚至更远。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-03
      • 2018-11-23
      • 2012-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多