【问题标题】:sscanf unexpected results inside a loopsscanf 循环内的意外结果
【发布时间】:2016-01-21 18:28:06
【问题描述】:

我正在尝试使用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)] = '\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 变量不是以空结尾的,所以我在末尾手动添加了 \0,然后我认为可能是buf 在循环中被覆盖,所以我使用 memset 重置它并重置所有 char 数组并检查所有内容的长度,但我看不出出了什么问题。任何帮助将不胜感激。

【问题讨论】:

  • 第一步:while (!feof (conf)) --> while (fgets (buf, sizeof buf, conf))
  • 第二步 id[strlen (id)] = '\0'; --> id[sizeof id - 1] = '\0'
  • 请注意,name[strlen (name)] = '\0'; 没有做任何有用的事情。 strlen 读取 name 直到找到 '\0',即使它比数组长。然后你将刚刚找到的'\0' 设置为'\0'
  • @chux 我很确定你的两个 cmets 实际上是他问题的答案。你应该发布它们。
  • 感谢 chux 和 Zan 的 cmets,但输出仍然相同

标签: c string fgets scanf


【解决方案1】:

正如 cmets 中所述,在调用 strncpy 之后,您没有正确地将空字节添加到 idname 的末尾。

来自手册页:

strncpy() 函数类似,只是不超过 n 个字节 的 src 被复制。因此,如果前 n 中没有空字节 src 的字节,结果不会以 null 结尾。

所以在使用strncpy 之后,您需要手动添加一个空字节作为数组的最后一个字节。您正在做的是使用 strlen 来查找字符串的长度。此函数仅在字符串正确以 null 终止的情况下才有效,在 strncpy 调用之后它可能不会。

所以不要这样:

id[strlen (id)] = '\0';
...
name[strlen (name)] = '\0';

这样做:

id[sizeof id - 1] = '\0';
...
name[sizeof name - 1] = '\0';

这会将空字节添加为最后一个字符。

现在解释一下你看到的行为:

当您第一次读取id 时,该数组的所有 8 个字节都填充了相关字符串的 8 个字节。它打印正确是因为nameid 之后立即出现在内存中(我将暂时解释我是如何知道这一点的)并且name 在循环之外被初始化为全零,所以name 的第一个字节(包含一个空字节)有效地终止id

然后当您在name 中读取时,id 的空终止符(实际上是在name 中)被覆盖。然后当您稍后打印id 时,它会打印来自id 的字节但找不到空字节,因此它会继续读取name 所在的字节,直到找到该字符串的空终止符并打印@987654341 @。 id 打印了这个事实就是我们知道name 紧跟在内存中id 之后的原因(在这种特殊情况下)。

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

【讨论】:

  • 这解释了一切。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多