循环内的 sscanf 意外结果

sscanf unexpected results inside a loop

我正在尝试使用 fgetssscanf:

解析这个简单的配置文件
# 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.

后,您没有正确地将空字节添加到 idname 的末尾

来自手册页:

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 个字节。它打印正确,因为 nameid 之后立即出现在内存中(我将立即解释我是如何知道这一点的)并且 name 在循环外被初始化为全零,所以第一个字节name 的(包含一个空字节)有效地终止了 id.

然后当您读入 name 时,id 的空终止符(实际上在 name 中)被覆盖。然后,当您稍后打印 id 时,它会打印来自 id 的字节,但没有找到空字节,因此它会继续读取 name 所在的字节,直到找到空终止符该字符串并打印 34DV4gx7Somebodyid 打印出来的事实就是我们如何知道 name 在内存中 id 之后立即出现(在这种特殊情况下)。

你看到 id 而不是 name 错误的原因是 id 对于你读入的字符串来说不够大(所以没有添加空终止符),但 name 对于它的字符串来说足够大(因此添加了一个空终止符)。