数组还是Malloc?


Answers:


28

主要区别在于VLA(可变长度数组)不提供检测分配失败的机制。

如果您声明

char result[len];

并且len超过可用堆栈空间量,程序的行为是不确定的。没有语言机制可以预先确定分配是否成功,或者在事后确定分配是否成功。

另一方面,如果您写:

char *result = malloc(len);
if (result == NULL) {
    /* allocation failed, abort or take corrective action */
}

那么您可以轻松地处理故障,或者至少保证您的程序在发生故障后不会尝试继续执行。

(好吧,主要是。在Linux系统上,malloc()即使没有相应的可用存储,也可以分配地址空间的大块;以后尝试使用该空间可以调用OOM Killer。但是检查malloc()故障仍然是一种好习惯。)

另一个问题,在许多系统,是,有更多的空间(可能是一个很大更多的空间),可用于malloc()比像沃拉斯自动对象。

正如Philip的答案已经提到的那样,C99中添加了VLA(特别是Microsoft不支持它们)。

并且VLA在C11中成为可选项。可能大多数C11编译器都会支持它们,但是您不能指望它。


14

可变长度自动数组在C99中引入了C。

除非您担心与旧标准的向后可比性,否则就可以。

一般来说,如果可以,请不要触摸它。不要提前优化。不必担心添加特殊功能或巧妙的处理方式,因为您通常不会使用它。把事情简单化。


7
我必须不同意“如果可行,请不要碰它”的格言。错误地认为某些代码“有效”会导致您解决某些“有效”代码中的问题。暂时必须暂时接受某些代码已经生效的信念。
布鲁斯·埃迪格

2
在制作一个可能更好的版本之前,请不要触摸它
H_7 2012年

8

如果您的编译器支持可变长度数组,则唯一的危险是在某些系统上,当堆len太大时,堆栈会溢出。如果您确定len不会大于某个特定数目,并且知道堆栈即使在最大长度下也不会溢出,则将代码保持不变;否则,用malloc和重写它free


在非c99函数上如何处理(char []){字符结果[sizeof(char)] =一些字符;通过网络发送结果}
开发人员袋

@DevBag char result [sizeof(char)]是一个大小数组1(因为sizeof(char)等于1),因此分配将被截断some chars
dasblinkenlight 2012年

对此感到抱歉,我是说函数(char str []){char result [sizeof(str)] =一些字符;通过网络发送结果}
开发人员袋

4
@DevBag这也不起作用- str 衰减到一个指针,所以它将sizeof是4或8,这取决于系统上的指针大小。
dasblinkenlight 2012年

2
如果您使用的C版本没有可变长度数组,则可以执行char* result = alloca(len);在堆栈上分配的。它具有相同的基本效果(和相同的基本问题)
使机器人

6

我喜欢这样的想法,即可以有一个运行时分配的数组,而不会出现内存碎片,悬挂指针等的情况。但是,其他人指出,这种运行时分配可以静默地失败。所以我在Cygwin bash环境中使用gcc 4.5.3进行了尝试:

#include <stdio.h>
#include <string.h>

void testit (unsigned long len)
{
    char result [len*2];
    char marker[100];

    memset(marker, 0, sizeof(marker));
    printf("result's size: %lu\n", sizeof(result));
    strcpy(result, "this is a test that should overflow if no allocation");
    printf("marker's contents: '%s'\n", marker);
}

int main(int argc, char *argv[])
{
    testit(100);
    testit((unsigned long)-1);  // probably too big
}

输出为:

$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'

在第二个调用中传递的过长长度显然导致了失败(溢出到marker []中)。这并不意味着这种检查是万无一失的(愚蠢的做法可能很聪明!)或它符合C99的标准,但是如果您对此有所顾虑,则可能会有所帮助。

和往常一样,YMMV。


1
+1这非常有用:3
Kokizzu 2014年

有一些代码与人们提出的主张一起总是很高兴的!谢谢^ _ ^
Musa Al-hassy'1

3

一般来说,堆栈是放置数据的最简单和最佳的位置。

我可以通过简单地分配您期望的最大数组来避免VLA的问题。

但是,在某些情况下,堆是最好的,并且搞乱malloc是值得的。

  1. 当其数量庞大但数据量可变时。很大程度上取决于您的环境>对于嵌入式系统> 1K,对于企业服务器> 10MB。
  2. 当您希望在退出例程后保留数据时,例如,如果返回指向数据的指针。使用
  3. 静态指针和malloc()的组合通常比定义大型静态数组更好。

3

在嵌入式编程中,当malloc和free操作频繁时,我们总是使用静态数组而不是malloc。由于嵌入式系统缺乏内存管理,频繁的alloc和free操作将导致内存碎片。但是,我们应该利用一些棘手的方法,例如定义数组的最大大小和使用静态局部数组。

如果您的应用程序在Linux或Windows上运行,则无论使用array还是malloc。关键在于您使用日期结构和代码逻辑的位置。


1

尚未有人提到的是,可变长度数组选项可能比malloc / free快得多,因为分配VLA只是调整堆栈指针的一种情况(至少在GCC中)。

因此,如果此函数是经常调用的函数(您当然会通过分析确定),则VLA是一个很好的优化选项。


1
直到将您推向栈外空间的情况时,它似乎都很好。更重要的是,可能不是您的代码真正达到了堆栈限制。它最终可能会咬入库或系统调用(或中断)。
Donal Fellows

@Donal Performance始终是内存与速度之间的权衡。如果您打算循环分配几兆字节的数组,那么即使有几千字节,只要函数不是递归的,这也是一个很好的优化。
JeremyP,2012年

1

这是我使用的非常常见的C解决方案,可能对您有所帮助。与VLA不同,在病理情况下,它不会面临任何实际的堆栈溢出风险。

/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
    /// Stores raw bytes for fast access.
    char fast_mem[512];

    /// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
    /// dynamically allocated memory address.
    void* data;
};

/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
    // Utilize the stack if the memory fits, otherwise malloc.
    mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
    return mem->data;
}

/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
    // Free the memory if it was allocated dynamically with 'malloc'.
    if (mem->data != mem->fast_mem)
        free(mem->data);
    mem->data = 0;
}

在您的情况下使用它:

struct FastMem fm;

// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);

// send result over network.
...

// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);

在上述情况下,如果字符串不超过512个字节,则使用堆栈。否则,它将使用堆分配。例如,如果在99%的时间里字符串适合512字节或更少的字节,这将很有用。但是,假设有一些疯狂的奇特案例,您可能偶尔需要处理字符串为32 KB的位置,而用户在键盘上睡着了之类的东西。这使得两种情况都可以毫无问题地处理。

我在生产中使用的实际版本也有其自己的等等版本,realloc以及calloc在同一概念上构建的符合标准的C ++数据结构,但是我提取了说明该概念所需的最低要求。

它确实有一个警告,那就是复制是很危险的,您不应该返回通过它分配的指针(由于FastMem实例被破坏,它们最终可能会失效)。它旨在用于局部函数范围内的简单情况,在这些情况下,您总是会尝试使用堆栈/ VLA,否则,在某些罕见情况下可能会导致缓冲区/堆栈溢出。它不是通用分配器,因此不应如此使用。

我实际上是在很早以前就创建它的,以响应使用C89的旧代码库中的情况,以前的团队认为永远不会发生,用户设法用长度超过2047个字符的名称来命名项目(也许他在键盘上睡着了) )。我的同事实际上试图将在各个位置分配的数组的大小增加到16384,以至于我当时觉得这太荒谬了,只是交换更大的堆栈溢出风险以换取较小的缓冲区溢出风险。这提供了一个非常容易插入的解决方案,只需添加几行代码即可解决这些问题。这样可以非常有效地处理常见情况,并且仍然可以利用堆栈,而不会出现那些使堆崩溃而导致软件崩溃的疯狂罕见情况。但是,我' 从那时起,甚至在C99之后,VLA仍然发现它很有用,因为VLA仍然无法保护我们免受堆栈溢出的侵害。但是,这个仍然可以从堆栈中为小分配请求池。


1

调用堆栈总是有限的。在Linux或Windows等主流操作系统上,限制为一兆字节或几兆字节(您可以找到更改它的方法)。对于某些多线程应用程序,它可能更低(因为可以使用较小的堆栈创建线程)。在嵌入式系统上,它可能只有几千字节。一个好的经验法则是避免呼叫帧大于几千字节。

因此,只有在您确定自己足够小(最多几万个)的情况下,使用VLA才有意义len。否则,您将发生堆栈溢出,这是未定义行为的情况,这是非常可怕的情况。

然而,使用手工时的动态存储器分配(例如callocmallocfree)也有其缺点:

  • 它可能会失败,您应该始终测试失败(例如callocmallocreturn NULL)。

  • 它的速度较慢:成功的VLA分配需要几纳秒的时间,一次成功malloc可能需要几微秒的时间(在好的情况下,只需几微秒的时间),甚至更长的时间(在涉及th动的病理情况下,甚至更多)。

  • 这很难编码:free只有当您确定不再使用该尖峰区域时,您才能进行编码。在您的情况下,您可以在同一例程中同时调用callocfree

如果您知道大部分时间result (您的名字很差,您永远都不应返回自动变量 VLA 的地址;因此,我使用的 buf不是result下面的内容)很小,则可以对它进行特殊处理,例如

char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf) 
  free(buf);

但是,以上代码可读性较低,可能是过早的优化。但是,它比纯VLA解决方案更强大。

PS。一些系统(例如,某些Linux发行版默认启用)具有内存过量使用malloc即使没有足够的内存也可以提供一些指针)。这是我不喜欢的功能,通常在我的Linux机器上禁用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.