当输入字符串长度超过 16 个字符时,程序将中止,但当我删除 realloc 并调用 malloc(sizeof(char) * 1000) 时,它就可以完美运行

valgrind 说,
valgrind: mmap(0x17a000, 708608) failed in UME with error 22 (Invalid argument). valgrind: this can be caused by executables with very large text, data or bss segments.
但是字符串是在堆上分配的

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

char *getstr()
{
    char *buf = malloc(sizeof(char));

    if (buf == 0)
        return 0;

    size_t i = 0;
    int tmp;

    while (1)
    {
        if (i == SIZE_MAX)
        {
            errno = ERANGE;
            buf[i] = 0;
            return buf;
        }
        tmp = getchar();
        switch (tmp)
        {
            case EOF:
            case '\n':
                buf[i] = 0;
            case 0:
                return buf;
        }
        buf[i] = tmp;

        if (realloc(buf, ++i) == 0)
        {
            free(buf);
            return 0;
        }
    }
}

int main()
{
    char *str = getstr();

    if (str == 0)
        return 1;

    printf("%s\n", str);
    free(str);
    return 0;
}

当我输入时它有效
abcdefg

但当我输入类似的东西时崩溃
abcdefghijklmnopqrstuvwxyz1234567890

5

  • 9
    请阅读realloc(buf, ++i)可能留下buf一个悬垂指针——您需要使用返回的值realloc


    – 

  • 3
    buf中的指针realloc(buf, ++i)始终无效,并且无论重新分配是否就位,使用它都是未定义的行为。您必须编写buf = realloc(buf, ++i)(或者如果您想在重新分配失败的情况下释放内存,则必须编写更繁琐的内容)。


    – 


  • 谢谢,现在它不会崩溃,但是字符串大小限制是 16 个字符。


    – 

  • 4
    你叫realloc(buf,++i)i第一次发生这种情况是什么情况?


    – 

  • 1
    @Displayname “但字符串大小限制为 16 个字符”:这到底是什么意思?当你做什么时究竟会发生什么?


    – 


最佳答案
3

if (realloc(buf, ++i) == 0)无法使用 的返回值realloc()

当使用旧版本时,这会导致未定义的行为(UB) 。buf


可以合理地假设第 16 次调用之后的返回值会有所不同,因为第一次调用在底层malloc(sizeof(char))分配了可能 16 个字符。

相反,使用返回值。

// if (realloc(buf, ++i) == 0) {
void *new_ptr = realloc(buf, ++i);
if (new_ptr == 0) {
  free(buf);
  return 0;
}
buf = new_ptr;

还存在其他问题,尚未重新启动 OP。

首先,你应该在每个“case”中添加“break;”。其次,在执行 realloc 的地方,你的代码应该看起来像这样

if((buff = realloc(buff, (++i) * sizeof(char))) == 0) 

。为了改进代码选项,您应该使用一些分配策略,因为现在您逐个分配元素。您应该使用特定大小的预定义块进行分配,或者使用如下分配策略:

 if(max_space >= 4){
    max_space = (max_space / 2) *  3)
 }
 else{
    max_space++;
 } 

现在你只需要检查 i == max_space 是否已填满所有空间。使用此方法,你的内存会增加,但不会太快,有时只会分配比需要多一点的空间。例如,前 4 次迭代有:“1、2、3、4”,之后有:“6、9、12、18、27、39”。也许最后如果你想节省空间,你可以这样做

  buff = realloc(buff, (i * sizeof(char)));

通过这样做,当您返回字符串时,它将仅使用您需要的空间,而无需每次都进行分配。

但是对于最大空间为 16 个字符的字符串,使用在 int main 中声明的普通数组可能会更好。当您需要动态数组并且不知道最大大小或大小很大(如 1000000 个元素)时,您应该使用 alloc。例如,当您必须在数组中注册每个客户时。您不知道这里的最大客户数量,因此您将使用动态数组。使用在 int main 中声明的数组,您的代码应该如下所示:

int getStr(char *str_buffer)
{
   //you’re code for string
   //return 0 if max size exceded else 1
}
int main(void)
{
   char buffer[16];
   if(getStr(buffer) == 0){
     printf(“Read failed”);
   }
   return 0;
}


 

让我们看一下前两个错误。(始终从报告的第一个错误开始)。

==4907== Invalid write of size 1
==4907==    at 0x2019F9: getstr (so18.c:33)
==4907==    by 0x201A53: main (so18.c:45)
==4907==  Address 0x5460041 is 0 bytes after a block of size 1 free'd
==4907==    at 0x4852371: realloc (vg_replace_malloc.c:1807)
==4907==    by 0x201A10: getstr (so18.c:35)
==4907==    by 0x201A53: main (so18.c:45)
==4907==  Block was alloc'd at
==4907==    at 0x484D2E4: malloc (vg_replace_malloc.c:450)
==4907==    by 0x2018D1: getstr (so18.c:8)
==4907==    by 0x201A53: main (so18.c:45)
  • “无效写入”表示您正在尝试写入不该写入的地方。该消息的接下来两部分解释了发生了什么。
  • “之后为 0 字节”表示它紧接着某些分配之后。
  • “大小为 1 的块被 realloc 释放…”意味着对 realloc 的调用负责释放。
  • “Block was alloc’d … at malloc”意味着释放的块最初是由 malloc 分配的。

将其与您的代码匹配

  1. bufmalloc被分配一个指针,该指针指向由开始处分配的 1 字节块getstr
  2. 读取一个字符,然后getchar将其写入该 1 字节块。
  3. realloc被调用,但正如其他答案所述,结果未分配给buf。这意味着buf现在指向已释放的 1 字节块(并且 realloc 分配的 2 字节块已泄漏)。
  4. 下一次迭代。第二个字符被读取getchar并写入buf[1]。这紧接着是 realloc 释放的大小为 1 的块。

我稍后会回顾这一点。

然后是

==4907== Invalid free() / delete / delete[] / realloc()
==4907==    at 0x4852371: realloc (vg_replace_malloc.c:1807)
==4907==    by 0x201A10: getstr (so18.c:35)
==4907==    by 0x201A53: main (so18.c:45)
==4907==  Address 0x5460040 is 0 bytes inside a block of size 1 free'd
==4907==    at 0x4852371: realloc (vg_replace_malloc.c:1807)
==4907==    by 0x201A10: getstr (so18.c:35)
==4907==    by 0x201A53: main (so18.c:45)
==4907==  Block was alloc'd at
==4907==    at 0x484D2E4: malloc (vg_replace_malloc.c:450)
==4907==    by 0x2018D1: getstr (so18.c:8)
==4907==    by 0x201A53: main (so18.c:45)
  • “无效释放”意味着您正在尝试释放不应该释放的东西。
  • “大小为 1 的块内的 0 个字节被释放…重新分配”与前一个类似,但这次指针指向块的开头。
  • “块在 malloc 中被分配…”与之前相同。

继续将其与代码匹配。

  1. 第二次调用realloc尝试再次释放 1 字节块。

第二个错误非常严重。这意味着从此时起堆已损坏。通常这意味着可执行文件将崩溃。

回到第一个错误。为什么在 1 字节块之后写入不会立即导致崩溃?这是因为堆分配有一个最小大小。这取决于您使用的 libc,但通常是 8 或 16 字节。它被硬编码到 Valgrind 中,因此没有命令行选项可以更改它。

这可能就是为什么直到字符串长度达到 16 才会崩溃的原因。