循环内的 sscanf 意外结果
sscanf unexpected results inside a loop
我正在尝试使用 fgets
和 sscanf
:
解析这个简单的配置文件
# configuration file for client
[user]
ID 34DV4gx7
NAME Somebody
我编写了以下脚本来解析它,其中 sscanf 最初似乎正确地提取了变量,然后由于某些未知原因将它们混淆了:
int main (void)
{
FILE *conf;
char *confname = "client.conf";
char buf[256], tmp[256];
char id[8];
char name[12];
char token[40];
size_t i, count = 0, valid = 0, len = sizeof token;
if ((conf = fopen (confname, "r")) == NULL)
{
fprintf (stderr, "Failed to open configuration file %s\n", confname);
return 1;
}
memset (id, 0, sizeof id);
memset (name, 0, sizeof name);
memset (token, 0, sizeof token);
while (!feof (conf))
{
memset (buf, 0, sizeof buf);
memset (tmp, 0, sizeof tmp);
if (fgets (buf, sizeof buf, conf) == NULL) continue;
if (buf[0] == '#' || buf[0] == '[') continue;
if (sscanf (buf, "ID %s", tmp) == 1)
{
strncpy (id, tmp, sizeof id);
id[strlen (id)] = '[=11=]';
printf ("id: %s[%d]\n", id, strlen (id));
valid++;
continue;
}
else if (sscanf (buf, "NAME %s", tmp) == 1)
{
strncpy (name, tmp, sizeof name);
name[strlen (name)] = '[=11=]';
printf ("name: %s[%d]\n", name, strlen (name));
valid++;
continue;
}
}
fclose (conf);
printf ("id: %s\n", id);
printf ("name: %s\n", name);
if (valid != 2) return 2;
for (i = 0; i < strlen (id) && count < len; i++) token[count++] = id[i];
token[count++] = ':';
for (i = 0; i < strlen (name) && count < len; i++) token[count++] = name[i];
token[count] = '[=11=]';
printf ("token: %s\n", token);
return 0;
}
结果:
id: 34DV4gx7[8]
name: Somebody[8]
id: 34DV4gx7Somebody
name: Somebody
token: 34DV4gx7Somebody:Somebody
预期:
id: 34DV4gx7[8]
name: Somebody[8]
id: 34DV4gx7
name: Somebody
token: 34DV4gx7:Somebody
我尝试了很多方法来找出导致这种行为的原因,但一无所获,我认为可能是 id 和 name 变量没有以 null 结尾,所以我在末尾手动添加了 \0 然后我认为它可以是 buf 在循环中被覆盖,所以我使用 memset 重置它并重置所有 char 数组并检查所有内容的长度,但我看不出出了什么问题。任何帮助将不胜感激。
如评论中所述,在调用 strncpy
.
后,您没有正确地将空字节添加到 id
和 name
的末尾
来自手册页:
The strncpy() function is similar, except that not more than n bytes
of src are copied. Thus, if there is no null byte among the first n
bytes of src, the result will not be null-terminated.
所以在使用strncpy
后需要手动添加一个空字节作为数组的最后一个字节。您正在做的是使用 strlen
来查找字符串的长度。此函数仅在字符串正确以 null 终止时才有效,而在 strncpy
调用之后它可能不是。
所以不是这个:
id[strlen (id)] = '[=10=]';
...
name[strlen (name)] = '[=10=]';
这样做:
id[sizeof id - 1] = '[=11=]';
...
name[sizeof name - 1] = '[=11=]';
这会将空字节添加为最后一个字符。
现在解释您所看到的行为:
当您第一次读入 id
时,该数组的所有 8 个字节都填充了相关字符串的 8 个字节。它打印正确,因为 name
在 id
之后立即出现在内存中(我将立即解释我是如何知道这一点的)并且 name
在循环外被初始化为全零,所以第一个字节name
的(包含一个空字节)有效地终止了 id
.
然后当您读入 name
时,id
的空终止符(实际上在 name
中)被覆盖。然后,当您稍后打印 id
时,它会打印来自 id
的字节,但没有找到空字节,因此它会继续读取 name
所在的字节,直到找到空终止符该字符串并打印 34DV4gx7Somebody
。 id
打印出来的事实就是我们如何知道 name
在内存中 id
之后立即出现(在这种特殊情况下)。
你看到 id
而不是 name
错误的原因是 id
对于你读入的字符串来说不够大(所以没有添加空终止符),但 name
对于它的字符串来说足够大(因此添加了一个空终止符)。
我正在尝试使用 fgets
和 sscanf
:
# configuration file for client
[user]
ID 34DV4gx7
NAME Somebody
我编写了以下脚本来解析它,其中 sscanf 最初似乎正确地提取了变量,然后由于某些未知原因将它们混淆了:
int main (void)
{
FILE *conf;
char *confname = "client.conf";
char buf[256], tmp[256];
char id[8];
char name[12];
char token[40];
size_t i, count = 0, valid = 0, len = sizeof token;
if ((conf = fopen (confname, "r")) == NULL)
{
fprintf (stderr, "Failed to open configuration file %s\n", confname);
return 1;
}
memset (id, 0, sizeof id);
memset (name, 0, sizeof name);
memset (token, 0, sizeof token);
while (!feof (conf))
{
memset (buf, 0, sizeof buf);
memset (tmp, 0, sizeof tmp);
if (fgets (buf, sizeof buf, conf) == NULL) continue;
if (buf[0] == '#' || buf[0] == '[') continue;
if (sscanf (buf, "ID %s", tmp) == 1)
{
strncpy (id, tmp, sizeof id);
id[strlen (id)] = '[=11=]';
printf ("id: %s[%d]\n", id, strlen (id));
valid++;
continue;
}
else if (sscanf (buf, "NAME %s", tmp) == 1)
{
strncpy (name, tmp, sizeof name);
name[strlen (name)] = '[=11=]';
printf ("name: %s[%d]\n", name, strlen (name));
valid++;
continue;
}
}
fclose (conf);
printf ("id: %s\n", id);
printf ("name: %s\n", name);
if (valid != 2) return 2;
for (i = 0; i < strlen (id) && count < len; i++) token[count++] = id[i];
token[count++] = ':';
for (i = 0; i < strlen (name) && count < len; i++) token[count++] = name[i];
token[count] = '[=11=]';
printf ("token: %s\n", token);
return 0;
}
结果:
id: 34DV4gx7[8]
name: Somebody[8]
id: 34DV4gx7Somebody
name: Somebody
token: 34DV4gx7Somebody:Somebody
预期:
id: 34DV4gx7[8]
name: Somebody[8]
id: 34DV4gx7
name: Somebody
token: 34DV4gx7:Somebody
我尝试了很多方法来找出导致这种行为的原因,但一无所获,我认为可能是 id 和 name 变量没有以 null 结尾,所以我在末尾手动添加了 \0 然后我认为它可以是 buf 在循环中被覆盖,所以我使用 memset 重置它并重置所有 char 数组并检查所有内容的长度,但我看不出出了什么问题。任何帮助将不胜感激。
如评论中所述,在调用 strncpy
.
id
和 name
的末尾
来自手册页:
The strncpy() function is similar, except that not more than n bytes of src are copied. Thus, if there is no null byte among the first n bytes of src, the result will not be null-terminated.
所以在使用strncpy
后需要手动添加一个空字节作为数组的最后一个字节。您正在做的是使用 strlen
来查找字符串的长度。此函数仅在字符串正确以 null 终止时才有效,而在 strncpy
调用之后它可能不是。
所以不是这个:
id[strlen (id)] = '[=10=]';
...
name[strlen (name)] = '[=10=]';
这样做:
id[sizeof id - 1] = '[=11=]';
...
name[sizeof name - 1] = '[=11=]';
这会将空字节添加为最后一个字符。
现在解释您所看到的行为:
当您第一次读入 id
时,该数组的所有 8 个字节都填充了相关字符串的 8 个字节。它打印正确,因为 name
在 id
之后立即出现在内存中(我将立即解释我是如何知道这一点的)并且 name
在循环外被初始化为全零,所以第一个字节name
的(包含一个空字节)有效地终止了 id
.
然后当您读入 name
时,id
的空终止符(实际上在 name
中)被覆盖。然后,当您稍后打印 id
时,它会打印来自 id
的字节,但没有找到空字节,因此它会继续读取 name
所在的字节,直到找到空终止符该字符串并打印 34DV4gx7Somebody
。 id
打印出来的事实就是我们如何知道 name
在内存中 id
之后立即出现(在这种特殊情况下)。
你看到 id
而不是 name
错误的原因是 id
对于你读入的字符串来说不够大(所以没有添加空终止符),但 name
对于它的字符串来说足够大(因此添加了一个空终止符)。