我正在尝试使用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)] = '\0';
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)] = '\0';
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] = '\0';
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数组并检查所有内容的长度,但我只是看不出有什么问题。任何帮助将不胜感激。
答案 0 :(得分:3)
正如评论中所提到的,在调用id
后,您没有正确地将空字节添加到name
和strncpy
的末尾。
从手册页:
strncpy()函数类似,但不超过n个字节 src的副本被复制。因此,如果前n个中没有空字节 src的字节,结果不会以空值终止。
因此在使用strncpy
之后,您需要手动添加空字节作为数组的最后一个字节。你正在做的是使用strlen
来查找字符串的长度。此函数仅在字符串正确空终止时才有效,该字符串在strncpy
调用之后可能不会。
所以不要这样:
id[strlen (id)] = '\0';
...
name[strlen (name)] = '\0';
这样做:
id[sizeof id - 1] = '\0';
...
name[sizeof name - 1] = '\0';
这会将空字节添加为最后一个字符。
现在解释你所看到的行为:
当您第一次读入id
时,此数组的所有8个字节都填充了相关字符串的8个字节。它打印正确是因为name
在id
之后立即出现在内存中(我将解释我是如何知道的)并且name
被初始化为循环外的所有零,因此第一个字节name
(包含空字节)有效地终止id
。
然后当您在name
中读取时,id
的空终止符(实际上在name
中)被覆盖。然后,当您稍后打印id
时,它会打印来自id
的字节但不会找到空字节,因此它会一直读取name
所在的字节,直到找到空终止符为止该字符串并打印34DV4gx7Somebody
。 id
打印此内容的事实是我们知道name
在内存id
之后立即出现(在此特定情况下)。
您看到id
而不是name
的错误的原因是因为id
对于您读入的字符串来说不够大(因此未添加空终结符),但是name
对于它的字符串来说足够大(所以添加了一个空终止符)。