【发布时间】:2011-01-16 09:45:30
【问题描述】:
清零内存有什么优势(即calloc() 优于malloc())?反正你不会把值改成别的吗?
【问题讨论】:
标签: c memory memory-management malloc calloc
清零内存有什么优势(即calloc() 优于malloc())?反正你不会把值改成别的吗?
【问题讨论】:
标签: c memory memory-management malloc calloc
有两个阵营:一个说在声明变量时初始化变量有助于发现错误。这个阵营中的人确保他们声明的所有内容都已初始化。他们将指向NULL、ints 的指针初始化为 0,等等。想法是一切都是确定的,当他们在调试器中看到 NULL 指针时,他们立即知道它没有正确设置。它还可以帮助您的程序在测试期间由于 NULL-pointer 取消引用而崩溃,而不是在生产运行中神秘地崩溃。
另一个阵营说,在声明时初始化变量会使事情更难调试,因为现在编译器无法警告您“未经设置就使用”的变量。
不告诉你我的个人喜好1:如果你属于第一阵营,你会想要calloc()而不是malloc()。如果你属于第二个阵营(显然你属于)那么你更喜欢malloc()而不是calloc()。
现在有两个例外:
calloc()而是malloc(),因为你正在初始化浮点数或指针,并且你知道所有位为零并不一定意味着@987654331 @为他们。或者,您不想要额外的开销。calloc() 并希望它全为零。例如,如果您想通过动态分配的m 计算n 的逐行总和int 数据。1你可以在 SO 上看到我对许多问题的回答,看看我属于哪个阵营:-)。
【讨论】:
NULL 指针,我会在循环中将它们设置为NULL。
brk() 或 sbrk() 系统调用(用于扩展内存分配的 Linux 系统调用)返回归零内存。堆管理器从 sbrk() 请求额外的内存。但是当本地分配释放某些东西时,堆管理器可能不会在后续分配之前将其清除 - 仅设置标头信息来管理它。
calloc使用指针结构:它们被初始化为 NULL。在我很久以前工作的实时过程控制系统中,我们决定让开机逻辑将所有 RAM 初始化为 0xCC,即 8086 的 interrupt 3 指令。如果处理器以某种方式执行未初始化的内存,这将导致处理器进入监视器(原始调试器)。 (不幸的是,8086 会愉快地执行包含零的内存,因为它们是 add [bx+si],al 指令。即使是 32 位模式也会导致它们成为 add [ax],al 指令。)
我不记得我们是否曾发现过失控程序,但对应于 0xCC 的值有多种:52,428(无符号 16 位)、-19,660(有符号 16 位)、-107374176(32 位浮点数)、并且 -9.25596313493e+61(64 位浮点数)出现在很多意想不到的地方。此外,一些期望字符为 7 位 ASCII 的代码(即一个错误)在尝试处理 0xCC 时提醒我们它的存在。
【讨论】:
calloc 将指针设置为NULL 是没有用的,因为标准不能保证全位为零 == NULL。浮点数也是如此。
(void *) 0 作为NULL,而IEEE 浮点格式都使用“所有位为零”作为真零。
(void *)0 不一定全为零。编译器必须将(void *)0 转换为适当的“空指针常量”。同样,当您编写p = 0; 并且p 是一个指针时,编译器必须将p 设置为一个等于空指针常量的位模式,这可能不是所有位都为零。
unsigned char 和 C99 固定大小类型 (u)intN_t 除外)允许具有填充位,将其设置为 0 可以是陷阱表示(对于远-如果存在反奇偶校验位,则所有位为零是奇偶校验违规)。不太可能,彻头彻尾的邪恶,但如果你按标准生活,那么你可以按标准死……
假设您要编写计数排序实现,或者深度优先搜索图并跟踪访问过的顶点。您将在算法运行时更新内存(而不是只分配一次值)。您需要在开始时将其初始化为零。如果您没有calloc,则必须手动检查它并在算法开始时将其初始化为零。 calloc 可能会更有效地为您做到这一点。
【讨论】:
很高兴知道您分配的任何内容都被初始化为零。许多错误来自使用未初始化内存的代码。另外,结构/类中的一些默认值可能为零,因此您不需要在 malloc 之后更改所有值。
例如,分配一个结构,其中包含一些带有 malloc 的指针。除非将它们设置为 NULL,否则 NULL 检查并不总是有效。如果你 calloc,你不必为指针值做额外的初始化步骤。
【讨论】:
calloc 将指针设置为 NULL 是没用的,因为标准不能保证全位为零 == NULL。
除了初始化变量的好处之外,calloc 还有助于跟踪错误。
如果您在未正确初始化的情况下意外使用了一些分配的内存,应用程序将始终以同样的方式失败。例如,来自空指针的访问冲突。使用 malloc 内存具有随机值,这可能导致程序以随机方式失败。
随机故障很难追踪,而 calloc 有助于避免这些。
【讨论】:
首先你不能调用指针,至少如果你想遵循标准 C 的话。
其次,当你用全零破坏成员时,错误就会被掩盖。最好有一个调试版本的 malloc,它将内存初始化为总是会崩溃的东西,例如 0xCDCDCDCD。
然后,当您看到 Access voilation 时,您会立即知道问题所在。 拥有 debug free 功能也是有益的,它会以不同的模式鞭打内存,这样那些在内存被释放后触摸内存的人会得到意想不到的惊喜。
在嵌入式系统上工作,调用只是为了“确定”通常不是一种选择。您通常一次性分配和填充,因此 calloc 只是让您双重记忆。
【讨论】:
没有人谈到性能方面,所以我想我必须这样做。如果您需要使用“以防万一”集成 memset 编写一个非常快速的程序 malloc 不是一个好方法。不管 memset 有多快,它总是太慢。有时你必须初始化一个向量或一个数组,所以真正的问题是控制你的时钟周期(即不浪费它们)。我曾经听过一句话“你永远不应该意外放弃性能”,这意味着从性能的角度来看,你必须始终知道为什么选择以一种或另一种方式实现代码(优缺点是什么以及如何权衡它们在特定情况下相互)。
如果您有一个将用字符串填充的缓冲区,那么在填充字符串之前对其进行初始化可能是“不错的”,但大多数人会同意这完全浪费了时钟周期。如果您正在编写一个新的 str* 函数,您可能希望 - 出于调试目的 - 用一个通常不应该出现的值填充缓冲区,但在分发时这将被删除。
正如其他人所提到的,如果正在访问未初始化的变量,编译器会发出警告,因此我认为最重要的是,“以防万一”初始化确实没有任何借口。
【讨论】: