对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
的返回值)谢谢您的回答!!我以后肯定会再次使用这些信息