【问题标题】:How to fix segfault caused by a realloc going out of bounds?如何修复由 realloc 越界引起的段错误?
【发布时间】:2019-09-13 13:40:17
【问题描述】:

您好,TIA 为您提供帮助。由于我不熟悉发布问题,因此我欢迎任何有关如何提出此问题的反馈。我在 SO 中进行了很多研究,但没有找到我认为我在寻找的东西。

我还在努力,我不太擅长 C。

我的目的是从给定 XML 的某些特定标签中提取数据并将其写入文件。我的问题出现了,因为当我尝试填充为此目的创建的数据结构时,realloc() 函数在某个时刻给了我一个指向超出范围的地址的指针。

如果你看这个例子

#include <stdio.h>

int main() {
    char **arrayString = NULL;
    char *testString;
    testString = malloc(sizeof("1234567890123456789012345678901234567890123456789"));
    strcpy(testString, "1234567890123456789012345678901234567890123456789");
    int numElem = 0;
    while (numElem < 50) {
        numElem++;
        arrayString = realloc(arrayString, numElem * sizeof(char**));
        arrayString[numElem-1] = malloc(strlen(testString)+1);
        strcpy(arrayString[numElem-1], testString);
    }
    printf("done\n");
    return 0;
}

它对我的代码做了类似但简化的事情。基本上尝试用 c 字符串填充 char** 但它会出现段错误。 (是的,我知道我使用的是 strcpy 而不是它更安全的替代方案,但据我所知,它会复制到 '\0',当你在“”之间写一个字符串时,它会自动包含在内,这就是我所需要的)

下面我会详细解释。

在这段代码中,我使用了 libxml2,但你不需要知道它来帮助我。

我有一个这样声明的自定义结构:

 struct List {
    char key[24][15];
    char **value[15];
    int size[15];
 };

struct List *list; //i've tried to make this static after reading that it could make a difference but to no avail

其中填充了必要的键值。 list-&gt;size[] 用零初始化,以跟踪我在 value 中插入了多少值。

value 是这样定义的,因为对于每个键,我需要一个 char* 数组来存储与其关联的每个值。 (我已经考虑过了,但这可能是一种错误的方法,欢迎提出建议 - 但这不是问题的目的)

我遍历 xml 文件,并且对于每个节点,我在节点名称和我的每个键之间执行 strcmp。当存在匹配时,该键的索引将用作value 矩阵中的索引。然后我尝试为 c 字符串矩阵扩展分配的内存,然后为单个 char* 扩展分配的内存。

“损坏”的代码,如下,其中

  • read是上述key的索引。
  • reader 是 xmlNode
  • string 包含 xmlNode 的名称,但随后被释放,因此将其视为新 char*
  • list 就是上面声明的结构体
if (xmlTextReaderNodeType(reader) == 3 && read >= 0)
    {
        /* pull out the node value */
        xmlChar *value;
        value = xmlTextReaderValue(reader);     
        if (value != NULL) {
            free(string);
            string=strdup(value);           
            /*increment array size */
            list->size[read]++;
            /* allocate char** */ list->value[read]=realloc(list->value[read],list->size[read] * sizeof(char**));
            if (list->value[read] == NULL)
                return 16;
            /*allocate string (char*) memory */
            list->value[read][list->size[read]-1] = realloc(list->value[read][list->size[read]-1], sizeof(char*)*sizeof(string));
            if (list->value[read][list->size[read]-1] == NULL)
                return 16;
            /*write string in list */
            strcpy(list->value[read][list->size[read]-1], string);
        }
        /*free memory*/
        xmlFree(value);
    }
    xmlFree(name);
    free(string);

我希望这会分配 char**,然后是 char*,但是在这段代码的几次迭代之后(这是一个包装在 while 循环中的函数),我得到了一个段错误。

用 gdb 分析这个(不是专家,只是在运行中学习它)我注意到确实代码似乎在 15 次迭代中按预期工作。在第 16 次迭代中,list-&gt;value[read][list-&gt;size[read]-1] 大小增加后,list-&gt;value[read][list-&gt;size[read]-1] 指向一个 0x51,标记为地址越界。 realloc 仅将其带到 0x3730006c6d782e31,仍标记为越界。我希望它指向最后分配的值。

这是一张图片:https://imgur.com/a/FAHoidp

如何正确分配所需的内存而不会超出范围?

【问题讨论】:

  • char *testString 没有分配存储空间,而您 strcpy!
  • 这个问题非常需要minimal reproducible example。你所有的sn-ps都有一些问题,不清楚要解决哪一个。
  • sizeof(testString) 将是指针的大小,使用strlen
  • 你说得对。让我修正那个例子。虽然我不需要修复它:-)
  • @AndrewHenle 如果他只打电话给sizeof(),它会为\0 留出空间...

标签: c pointers memory libxml2


【解决方案1】:

你的代码有很多问题:

  1. 您没有包括所有适当的标题。你是怎么得到这个编译的?如果您使用mallocrealloc,则需要#include &lt;stdlib.h&gt;。如果您使用strlenstrcpy,则需要#include &lt;string.h&gt;
  2. 并不是一个错误,但除非您将 sizeof 应用于类型本身,否则您不必使用括号。
  3. 停止使用sizeof str 来获取字符串的长度。正确且安全的方法是strlen(str)+1。如果有一天你将sizeof 应用于指针,你会遇到麻烦。
  4. 不要使用sizeof(type) 作为malloccallocrealloc 的参数。相反,请使用sizeof *ptr。这将避免您不正确的numElem * sizeof(char**),而是将其替换为numElem * sizeof *arrayString,正确转换为numElem * sizeof(char*)。不过,这一次,sizeof(char**) == sizeof(char*) 的纯属巧合拯救了你,至少在 GCC 上是这样。
  5. 如果您正在动态分配内存,您还必须在不再需要它时手动解除分配。为此目的使用freefree(testString);free(arrayString);
  6. 这不是一个错误,但如果您想循环遍历元素,请使用for 循环,而不是while 循环。这样,每位读者都知道您的意图。

这段代码在 GCC 上编译得很好:

#include <stdio.h> //NULL, printf
#include <stdlib.h> //malloc, realloc, free
#include <string.h> //strlen, strcpy

int main()
{
    char** arrayString = NULL;
    char* testString;
    testString = malloc(strlen("1234567890123456789012345678901234567890123456789") + 1);
    strcpy(testString, "1234567890123456789012345678901234567890123456789");
    for (int numElem = 1; numElem < 50; numElem++)
    {
        arrayString = realloc(arrayString, numElem * sizeof *arrayString);
        arrayString[numElem - 1] = malloc(strlen(testString) + 1);
        strcpy(arrayString[numElem - 1], testString);
    }
    free(arrayString);
    free(testString);
    printf("done\n");
    return 0;
}

【讨论】:

  • 感谢您指出错误。如您所见,我仍在学习。第 2 点很奇怪,因为我能够在我的机器上编译它。我了解第 6 点,但编译器问题不允许我使用 for 循环。您能解释一下第 3 点和第 4 点吗?
  • @9Snick4 第 3 点:声明一个 char* 指针并使用 malloc 为其分配内存。然后,使用strcpy 将字符串复制到其中。如果您在指针上使用strlen,您将获得不含NULL 字符的字符串长度。如果你使用sizeof,你会得到sizeof(char*),这不是你想要的。
  • 第 2 点是一个错误,我在编写时编译了错误的代码。正确的说法是:您不必将对象应用到 () 中时将sizeof 括在其中,但如果您愿意,您可以。如果您将sizeof 应用于一个类型,那么您必须使用 () 将其括起来。
  • @9Snick4 第 4 点:使用 malloc 时不要将 sizeof 应用于指针的类型,而是应用于您指向的类型。当你应该使用sizeof(char*)时,你在malloc中使用了sizeof(char**)。为了避免这样的错误,你可以简单地使用sizeof *ptr,编译器会为你得到正确的类型。
  • 感谢您抽出宝贵时间给我写这封信。感谢您与我分享您的专业知识。
猜你喜欢
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-06
  • 1970-01-01
  • 1970-01-01
  • 2014-10-31
  • 2015-12-08
相关资源
最近更新 更多