malloc的优点是什么?

3

为一些数据分配内存的优点是什么?我们为什么不能直接使用它们的数组呢?

例如:

 int *lis;
 lis = (int*) malloc ( sizeof( int ) * n );

 /* Initialize LIS values for all indexes */
 for ( i = 0; i < n; i++ )
 lis[i] = 1;

我们本可以使用普通的数组。
我不太理解malloc的工作原理,它实际上是做什么的。因此,对它们进行解释对我来说更有益。
假设我们在上面的代码中用n替换sizeof(int) * n,然后尝试存储整数值,我可能会遇到哪些问题?并且是否有一种方法可以直接从分配的内存空间中打印存储在变量(例如这里的lis)中的值?

在极少数情况下可能会使用普通数组,但除非“n”是编译时常量,否则普通数组将无法正常工作。 std :: vector可以(std :: unique_ptr <int []>也可以)。 - chris
1
我建议你通过谷歌搜索来了解malloc的作用,而且我不建议在任何代码中使用它...相反,应该使用共享指针来处理内存管理。 - AngryDuck
如果你没有分配足够的内存,你会在被分配内存之外写入数据。你肯定不认为这是个好主意吧。这将是未定义行为——任何事情都有可能发生。 - Bernhard Barker
malloc和C++并不是最好的朋友。 - Marc Claesen
简单的答案是,在C++中,除非你正在实现自定义分配器,否则你永远不会使用malloc。在这种情况下,唯一可接受的解决方案是std::vector<int> lis( n ); - James Kanze
6个回答

6
你的问题似乎更倾向于比较动态分配的C风格数组和可变长度数组,这可能是你正在寻找的内容:为什么可变长度数组不是C++标准的一部分? 然而, 标签提供了最终答案:使用 std::vector 对象代替。 只要可能,避免动态分配和丑陋的内存管理责任 ~> 尝试利用具有自动存储期的对象。另一个有趣的阅读材料可能是:理解术语和概念的含义 - RAII(资源获取即初始化)

"如果我们在上述代码中用n替换sizeof(int) * n,然后尝试存储整数值,我可能会遇到什么问题?"
- 如果您仍然认为n是可以存储在此数组中的整数数量,则很可能会遇到未定义行为


3
答案很简单。本地数组分配在你的栈上,它是为你的程序预分配的一小块内存。一旦超过几千个数据,你就无法在栈上完成大量操作。对于更多的数据,你需要从栈中分配内存。
这就是malloc的作用。
malloc按照你的要求分配一块内存。它返回一个指向该内存起始位置的指针,该指针可以被视为一个数组。如果你超出了该内存的大小,结果是未定义行为。这意味着可能一切正常,也可能你的计算机会炸掉。不过,最有可能的是你会得到一个段错误。
从内存中读取值(例如打印)与从数组中读取值相同。例如 printf("%d", list[5]);。
在C99之前(我知道这个问题标记为C++,但可能你正在学习编译成C ++的C语言),还有另一个原因。你不能在栈上有一个可变长度的数组。即使现在,在堆栈上使用可变长度数组也没有那么有用,因为堆栈太小。因此,你需要使用malloc函数来分配所需大小的内存,其大小在运行时确定,以获取可变数量的内存。
本地数组或任何本地变量的另一个重要区别是对象的生命周期。本地变量在其作用域结束后就无法访问。malloc的对象一直存在,直到被free。这对于几乎所有不是数组的数据结构至关重要,例如链表、二叉搜索树(及其变体)、(大多数)堆等。
一个使用malloc的对象的例子是文件。一旦你调用fopen,保存与打开文件相关的数据的结构就会使用malloc进行动态分配,并作为指针(FILE *)返回。
注:非本地数组(全局或静态)在执行之前已经分配,因此它们无法在运行时确定长度。

函数外定义的数组怎么办? - trojanfoe
@trojanfoe,我加了一条注释。 - Shahbaz
你似乎在断言数组的区别在于其可容纳的数据量,但这并不正确;静态分配的数组和堆上分配的数组一样可以容纳大量数据。关键的区别在于它们的大小可以在运行时确定,并且可以创建具有任意复杂度的新对象(如struct);而使用静态定义的数组则无法实现这一点。 - trojanfoe
@trojanfoe,按标准来说可能是这样,但实际上没有一种实现是这样的。随着C99中可变长度数组的引入,您的第一个观点不再是问题。第二个观点并不完全正确。新对象可以很容易地作为局部变量创建。只有在将它们的生命周期延长到本地存储之外时才需要malloc,而不仅仅是分配它们。我也会写一些关于这方面的内容。 - Shahbaz

3

更根本的问题在于,除了堆栈和变量常量的问题之外(并且除了你本来就不应该在C++中使用malloc()之外),当函数退出时本地数组将不再存在。如果你返回一个指向它的指针,那么这个指针一旦被调用者接收就会变得无用,然而使用malloc()new动态分配的内存仍然有效。例如,你不能使用本地数组实现类似strdup()这样的函数,也不能明智地实现一个链表或树形结构。


1
我假设您正在询问c语言中maloc()的目的: 假设您想从用户那里获取一个输入,然后为该大小分配一个数组:
int n;
scanf("%d",&n);
int arr[n];

这会失败,因为n在编译时不可用。现在可以使用malloc()函数。
int n;
scanf("%d",&n);
int* arr = malloc(sizeof(int)*n);

实际上,malloc() 在堆区域动态分配内存。

  1. 你需要将大小乘以 n
  2. 不要使用 C 风格的强制转换。
  3. 不要使用 malloc,但可以使用其他方法。
- user1804599
2
如果您正在编程C语言,则应使用C风格的转型和malloc函数,但不要将其返回值强制转换。 - zwol

0

我们本可以使用普通数组

C++中(至少今年),数组具有静态大小;因此,从运行时值创建一个数组:

int lis[n];

不允许使用“p”标签。一些编译器允许这种非标准扩展,但它将在明年成为标准;但是,现在如果我们想要一个动态大小的数组,我们必须动态分配它。

在C语言中,这意味着要使用malloc函数;但是你问的是关于C++,所以你需要

std::vector<int> lis(n, 1);

分配一个包含初始化为1的int值的大小为n的数组。

(如果您愿意,您可以使用new int[n]分配数组,并在完成后记得用delete[] lis释放它,并且要特别小心不要泄漏内存,如果抛出异常; 但是生命太短暂了,不值得这样做。)

好吧,我不完全明白 malloc 的工作原理,它实际上是做什么的。因此,为我解释它们将更有益处。

C语言中的malloc和C++中的new都从“自由存储区”分配持久性内存。与局部变量的内存不同,它会在变量超出范围时自动释放,但这种内存会一直存在,直到您明确释放它(在C中使用free,在C++中使用delete)。如果需要数组超出当前函数调用的生命周期,这是必需的。如果数组非常大,则这也是个好主意:局部变量(通常)存储在堆栈上,堆栈大小有限。如果溢出,程序将崩溃或出现其他问题。(而且,在当前标准的C++中,如果大小不是编译时常量,则这是必需的。)

假设我们在上面的代码中用 n 替换 sizeof(int) * n,然后尝试存储整数值,我可能会遇到什么问题?
你没有为 n 个整数分配足够的空间;因此,假设您有的代码将尝试访问超出分配空间末尾的内存。这将导致未定义的行为;如果你很幸运,会崩溃,如果你不幸,会导致数据损坏。
那么有没有办法直接从分配的内存空间中打印变量存储的值,例如这里是 lis
你的意思是像这样吗?
for (i = 0; i < len; ++i) std::cout << lis[i] << '\n';

0
一些旧的编程环境根本没有提供malloc或任何等效的功能。如果需要动态内存分配,您必须在巨大的静态数组之上自己编写代码。这有几个缺点:
  • 静态数组大小对程序一次性处理多少数据放置了硬性上限,而不需要重新编译。如果你曾经尝试在TeX中做一些复杂的事情,并收到“容量超限,抱歉”消息,那么这就是原因。
  • 操作系统(如它所是)必须一次性为静态数组保留空间,无论是否会全部使用。这种现象导致了“过度承诺”,即操作系统假装已经分配了你可能需要的所有内存,但如果你实际上尝试使用超过可用内存的数量,它将终止你的进程。为什么有人想要这样做呢?然而,在90年代中期的商业Unix中,这被吹嘘为一项功能,因为它意味着巨大的FORTRAN模拟可以在小实例大小上进行测试,而不会出现问题,即使它们潜在地需要比你微不足道的Sun工作站更多的内存。(假设你会在实际上有足够内存应对的Cray上运行实例。)
  • 动态内存分配器很难实现。看看jemalloc paper,就能体会到它有多么棘手。(如果你想要自动垃圾回收,它会变得更加复杂。)这正是你希望大师为每个人的利益编写一次的东西。

现在,即使是相当基本的嵌入式环境也会提供某种形式的动态分配器。

然而,尝试不使用动态内存确实是一种很好的思维训练。过度使用动态内存会导致效率低下,这种效率问题通常很难在事后消除,因为它已经融入了架构中。如果看起来手头的任务不需要动态分配,那么可能确实不需要。

然而,如果你真的应该使用动态内存分配,而没有使用它,可能会引起自己的问题,例如对字符串长度施加硬性上限,或将非可重入性编入API中(请比较gethostbynamegetaddrinfo)。

所以你必须仔细考虑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接