我在应用程序中使用以下代码,并且工作正常。但是我想知道使用malloc还是将其保留为更好?
function (int len)
{
char result [len] = some chars;
send result over network
}
我在应用程序中使用以下代码,并且工作正常。但是我想知道使用malloc还是将其保留为更好?
function (int len)
{
char result [len] = some chars;
send result over network
}
Answers:
主要区别在于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编译器都会支持它们,但是您不能指望它。
如果您的编译器支持可变长度数组,则唯一的危险是在某些系统上,当堆len
太大时,堆栈会溢出。如果您确定len
不会大于某个特定数目,并且知道堆栈即使在最大长度下也不会溢出,则将代码保持不变;否则,用malloc
和重写它free
。
char result [sizeof(char)]
是一个大小数组1
(因为sizeof(char)
等于1),因此分配将被截断some chars
。
str
衰减到一个指针,所以它将sizeof
是4或8,这取决于系统上的指针大小。
char* result = alloca(len);
在堆栈上分配的。它具有相同的基本效果(和相同的基本问题)
我喜欢这样的想法,即可以有一个运行时分配的数组,而不会出现内存碎片,悬挂指针等的情况。但是,其他人指出,这种运行时分配可以静默地失败。所以我在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。
尚未有人提到的是,可变长度数组选项可能比malloc / free快得多,因为分配VLA只是调整堆栈指针的一种情况(至少在GCC中)。
因此,如果此函数是经常调用的函数(您当然会通过分析确定),则VLA是一个很好的优化选项。
这是我使用的非常常见的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仍然无法保护我们免受堆栈溢出的侵害。但是,这个仍然可以从堆栈中为小分配请求池。
该调用堆栈总是有限的。在Linux或Windows等主流操作系统上,限制为一兆字节或几兆字节(您可以找到更改它的方法)。对于某些多线程应用程序,它可能更低(因为可以使用较小的堆栈创建线程)。在嵌入式系统上,它可能只有几千字节。一个好的经验法则是避免呼叫帧大于几千字节。
因此,只有在您确定自己足够小(最多几万个)的情况下,使用VLA才有意义len
。否则,您将发生堆栈溢出,这是未定义行为的情况,这是非常可怕的情况。
然而,使用手工时的动态存储器分配(例如calloc
或malloc
&free
)也有其缺点:
它可能会失败,您应该始终测试失败(例如calloc
或malloc
return NULL
)。
它的速度较慢:成功的VLA分配需要几纳秒的时间,一次成功malloc
可能需要几微秒的时间(在好的情况下,只需几微秒的时间),甚至更长的时间(在涉及th动的病理情况下,甚至更多)。
这很难编码:free
只有当您确定不再使用该尖峰区域时,您才能进行编码。在您的情况下,您可以在同一例程中同时调用calloc
和free
。
如果您知道大部分时间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机器上禁用。