对sscanf的使用缺乏了解

对sscanf的使用缺乏了解,c,parsing,scanf,C,Parsing,Scanf,我想分析一个特定的行。因此,为了测试逻辑,我编写了以下代码,但我可能理解错误: 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[] = "

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

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

第一个实际读数为1.0.0!然而,问题是tu8UVersion不是以null结尾的,因此printf(不是sscanf)打印超出字段范围(但是,正如sjsam所指出的那样,这样做是未定义的行为)——紧接着是tu8UCommit(不一定必须如此,出于对齐原因,这两者之间可能仍然有一些填充字节!)

您最多需要打印5个字符(
%.5s
,以printf格式字符串打印),或者按照注释中的建议,保留将tu8UVersion终止为0的位置


您的缓冲区也可能发生类似的情况。幸运的是,它们似乎已经被初始化为0(可能是因为编译为调试版本),这也不一定会发生。因此,如果运气不好,您可能已经打印了buf1的全部剩余内容(被丢弃在垃圾箱中)甚至更多。

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

如果要扫描事先不知道大小的版本,可以将扫描的字符限制为小数位数和带有
%[.-9]
的点,或者限制为除带有
%[^-]
的连字符以外的所有字符。(除了必须在括号中提供有效字符的列表外,
%[…]
格式类似于
%s
。插入符号作为第一个字母表示字符串由未列出的字符组成。换句话说,
%s
%[^\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;
}
#包括
#包括
typedef结构版本
{
煤焦-tu8UVersion[12];
char tu8UCommit[32];
}tst_prg_版本;
int main(int argc,字符**argv)
{
tst_prg_版本1版本;
char str[]=“BOARD-VERS-v1.0.0-git+9abc12345a”;
int n;
n=sscanf(str,“BOARD-VERS-v%11[^-]-git+%s”,
lstVer.tu8UVersion,lstVer.tu8UCommit);
如果(n==2){
printf(“版本='%s'\n”,lstVer.tu8UVersion);
printf(“提交=“%s”\n”,lstVer.tu8UCommit);
}否则{
puts(“解析错误”);
}
返回0;
}
为什么第一个vers等于1.0.09abc12345a而不是1.0.0

记住你有

typedef struct vers
{
   char tu8UVersion[5];
   char tu8UCommit[32];
}tst_prg_versions;
我猜,
tu8UVersion
tu8UCommit
的内存很有可能是连续的。因为您在执行以下操作时,不以null结尾:

printf("vers='%s'\n", lstVer.tu8UVersion);
它继续打印
tu8UCommit
,并停止打印,因为
tu8UCommit
以null结尾

虽然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));

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