【问题标题】:Invalid write when initializing an array of linked lists初始化链表数组时写入无效
【发布时间】:2020-07-04 12:38:08
【问题描述】:

所以我有这个函数来分配和初始化NULL,这将是一个链表数组。我实际上希望这个函数返回一个NULL 指针数组,以便以后可以用链表填充它。

static t_tokens **init_tokens_groups(size_t size)
{
    t_tokens **toks_groups;
    if (!(toks_groups = malloc(sizeof(toks_groups) * size + 1)))
        exit(EXIT_FAILURE);
    while (size + 1)
    {
        printf("size: %zu\n", size);
            toks_groups[size] = NULL;
            size--;
    }
    return (toks_groups);
}

它工作正常,但是当我在 Valgrind 中运行我的程序(这是一个极简的 shell)时,

valgrind --track-origins=yes ./mysh

我遇到了这个问题:

==4914== Invalid write of size 8
==4914==    at 0x10AD6B: init_tokens_groups (tokens_split.c:39)
==4914==    by 0x10AE06: split_tokens (tokens_split.c:68)
==4914==    by 0x1093D9: prompt_loop (sh21.c:38)
==4914==    by 0x10944A: main (sh21.c:57)
==4914==  Address 0x4a508f8 is 8 bytes inside a block of size 9 alloc'd
==4914==    at 0x483A7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==4914==    by 0x10AD42: init_tokens_groups (tokens_split.c:35)
==4914==    by 0x10AE06: split_tokens (tokens_split.c:68)
==4914==    by 0x1093D9: prompt_loop (sh21.c:38)
==4914==    by 0x10944A: main (sh21.c:57)
==4914== 
==4914== Invalid write of size 8
==4914==    at 0x10AD6B: init_tokens_groups (tokens_split.c:39)
==4914==    by 0x10AE06: split_tokens (tokens_split.c:68)
==4914==    by 0x109369: dispatch (sh21.c:19)
==4914==    by 0x1093F6: prompt_loop (sh21.c:42)
==4914==    by 0x10944A: main (sh21.c:57)
==4914==  Address 0x4a509f8 is 8 bytes inside a block of size 9 alloc'd
==4914==    at 0x483A7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==4914==    by 0x10AD42: init_tokens_groups (tokens_split.c:35)
==4914==    by 0x10AE06: split_tokens (tokens_split.c:68)
==4914==    by 0x109369: dispatch (sh21.c:19)
==4914==    by 0x1093F6: prompt_loop (sh21.c:42)
==4914==    by 0x10944A: main (sh21.c:57)

我真的不明白它是从哪里来的,因为我只是在这个阶段初始化那些指向NULL 的指针,而且我在填充时没有任何问题或错误(嗯,不是我检测到的),在程序中操作和读取那些链表数组。我假设我对内存做了一些奇怪的事情,但我不知道在哪里。

【问题讨论】:

  • size +1 在对malloc 的调用中必须是(size+1)
  • 什么是toks_groups?它与t_tokens 有什么关系?虽然@DYZ 提到的问题可能是导致 Valgrind 错误的原因,但请尝试创建一个minimal reproducible example 以向我们展示。
  • 在不相关的注释中,您的循环 while (size + 1) 非常奇怪。为什么不简单地使用for (size_t i = 0; i < size + 1; ++i),这是用于迭代一系列值(如数组索引)的最常见和标准的循环类型。
  • @Someprogrammerdude 我以后会做的。此外,这是一个学校项目,出于某些原因(我不明白),我们不允许使用“for”循环。
  • @Zest 不允许for 循环似乎是一些教师似乎喜欢的那些奇怪且通常是任意的限制之一,尤其是当它与作业本身无关时。无论如何,for 看起来像 for (init; cond; increment) { body } 可以翻译成 init; while (cond) { body; increment }

标签: c memory valgrind allocation


【解决方案1】:

这个:

toks_groups = malloc(sizeof(toks_groups) * size + 1))

是错误的,并且会在您第一次执行toks_groups[size] = NULL;(在分配的区域之外)时导致问题。 Valgrind 告诉你无效写入是 8 个字节,因为你系统上的指针大小是 8 个字节。

如果你想分配size + 1 元素,你应该把表达式放在括号中。你在做sizeof(toks_groups) 的事实也很奇怪,没有多大意义。您可能想要做的是:

toks_groups = malloc(sizeof(*toks_groups) * (size + 1)))

如果您只想分配size 元素(而不是size + 1),则从malloc() 中删除+ 1 并更改while (...) 条件。您也根本不需要while,如果您想将所有内容初始化为NULL,您可以使用calloc() 而不是malloc()

另外,根据经验:

  1. 使用有意义的名称。调用 size 显然不是大小,而只是一些元素,会让人感到困惑。
  2. 使用正确的代码结构。使用 while 循环迭代一系列值确实违反直觉,很容易导致错误。

上述代码的更好版本如下:

static t_tokens **init_tokens_groups(size_t n)
{
    t_tokens **toks_groups;

    if (!(toks_groups = calloc(sizeof(*toks_groups) * (n + 1))))
        exit(EXIT_FAILURE);

    return toks_groups;
}

我仍然不确定您是否真的需要附加元素,但您应该知道。

【讨论】:

  • 感谢您花时间回答 Marco。问题确实在于括号中没有“+ 1”。这是一个学校项目,我只能使用有限数量的现有功能,而“calloc”不是其中的一部分。由于一些奇怪的原因,我也不允许使用“for”循环。我使用额外的元素在我的数组末尾保留一个额外的 NULL 指针,以便在迭代时将其用作停止
猜你喜欢
  • 1970-01-01
  • 2018-03-01
  • 2011-02-15
  • 1970-01-01
  • 2014-07-03
  • 1970-01-01
  • 2021-08-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多