当输入字符串长度超过 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
最佳答案
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 分配的。
将其与您的代码匹配
buf
malloc
被分配一个指针,该指针指向由开始处分配的 1 字节块getstr
。- 读取一个字符,然后
getchar
将其写入该 1 字节块。 realloc
被调用,但正如其他答案所述,结果未分配给buf
。这意味着buf
现在指向已释放的 1 字节块(并且 realloc 分配的 2 字节块已泄漏)。- 下一次迭代。第二个字符被读取
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 中被分配…”与之前相同。
继续将其与代码匹配。
- 第二次调用
realloc
尝试再次释放 1 字节块。
第二个错误非常严重。这意味着从此时起堆已损坏。通常这意味着可执行文件将崩溃。
回到第一个错误。为什么在 1 字节块之后写入不会立即导致崩溃?这是因为堆分配有一个最小大小。这取决于您使用的 libc,但通常是 8 或 16 字节。它被硬编码到 Valgrind 中,因此没有命令行选项可以更改它。
这可能就是为什么直到字符串长度达到 16 才会崩溃的原因。
|
realloc(buf, ++i)
可能留下buf
一个悬垂指针——您需要使用返回的值realloc
。–
buf
中的指针realloc(buf, ++i)
始终无效,并且无论重新分配是否就位,使用它都是未定义的行为。您必须编写buf = realloc(buf, ++i)
(或者如果您想在重新分配失败的情况下释放内存,则必须编写更繁琐的内容)。–
–
realloc(buf,++i)
。i
第一次发生这种情况是什么情况?–
–
|