calloc -- 清零内存的用处

12

在IT技术中,使用calloc()比malloc()更能发挥优势的原因是什么?不管怎样,您都会把值更改为其他内容吗?

7个回答

20
有两派人:一派认为在声明变量时初始化能够帮助发现错误。这些人确保他们声明的所有变量都被初始化,将指针初始化为NULL,将int初始化为0等。他们认为一切都是确定的,当他们在调试器中看到一个NULL指针时,立即就知道它没有被正确设置。这也可以帮助你的程序在测试期间因NULL指针解引用而崩溃,而不是在生产运行中神秘地崩溃。
另一派人认为,在声明变量时初始化会使调试变得更加困难,因为现在编译器不能警告你关于“使用未设置”的变量了。
不透露个人偏好1:如果你属于第一派人,你会想使用calloc()代替malloc()。如果你属于第二派人(显然你是),那么你更喜欢使用malloc()而不是calloc()
现在有两个例外:
  • 如果你属于“初始化一切”阵营,则不使用calloc()而使用malloc(),因为你正在初始化浮点数或指针,并且你知道所有零位并不一定意味着0。或者,你不想增加额外的开销。
  • 如果你属于“需要时设置”阵营,当你分配一些数据并希望它们都是零时,可能希望使用calloc()。例如,如果你想计算动态分配的为n乘以mint数据的逐行总和。

1您可以查看我在此处关于SO的许多问题的答案,以确定我属于哪个阵营 :-)。


3
所有IEEE浮点格式都定义了“所有位为零”的特殊情况表示真正的零。 - wallyk
@akillio:虽然这几乎是一个哲学问题:你是按照标准编程,还是按照你所知道的实现方式编程?就我个人而言,如果我正在编写可移植代码,我会尽量按照标准编程:这比了解1970年以来制造的每台计算机要容易。过去,编译器做出了一些我认为“永远不会发生”的事情,让我感到非常惊讶。其中一个例子是具有中间端数据类型的ARM ABI,ffs。显然,0仍然是按位0,所以这不是你要求的例子。 - Steve Jessop
2
@Steve:brk()sbrk()系统调用(Linux系统调用扩展内存分配)出于安全考虑返回清零的内存。堆管理器从sbrk()请求额外的内存。但是当本地分配释放某些内容时,堆管理器可能在后续分配之前不会清除它——只设置头信息以进行管理。 - wallyk
点赞,因为客观地呈现了一个有趣且相关的争议 :) - Merlyn Morgan-Graham
@Michael:http://catb.org/jargon/html/M/middle-endian.html - 但它提供的信息非常少。 - Alok Singhal
显示剩余6条评论

8
  1. 了解已有的值后,程序员可以采取一些捷径并进行某些优化。最常见的是使用calloc来分配指针结构:它们会被初始化为空。
  2. 如果程序员在分配中忘记初始化某些内容怎么办?与随机内容不同,0是一个很好的默认值。

很久以前我曾经参与过实时处理控制系统的工作,我们决定让开机逻辑将所有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位浮点数)。此外,当一些代码试图处理0xCC时,一些期望字符是7位ASCII码的错误也提醒了我们。


2
使用calloc函数将指针设置为NULL是无用的,因为标准不能保证所有位为零等于NULL。同样适用于浮点数。 - Alok Singhal
2
自从20世纪70年代末,这种说法就不再正确了。所有重要的架构都使用(void *)0作为NULL,IEEE浮点格式都将“位全零”作为真正的零。 - wallyk
6
(void *)0 不一定全部为零。编译器必须将 (void *)0 转换为适当的“空指针常量”。同样,当你写 p = 0; 并且 p 是一个指针时,编译器必须将 p 设置为一个等于空指针常量的比特模式,这个常量不一定是所有位都为零。 - Alok Singhal
1
同样,整数类型(除了unsigned char和C99固定大小类型(u)intN_t之外),允许具有填充位,将其设置为0可能会成为陷阱表示(例如,如果存在反奇偶校验位,则全零位是奇偶校验错误的情况)。虽然不太可能出现,但如果你遵循标准,那么你就必须按照标准去执行... - Steve Jessop
2
NULL的二进制表示不依赖于架构(据我所知),而是编译器供应商的选择。我过去使用的旧版Watcom C编译器(在x86上)用0xffffffff表示空指针。 - Secure
显示剩余6条评论

3
假设您想编写计数排序实现,或深度优先搜索图并跟踪访问的顶点。您将在算法运行时更新内存(而不是仅分配一次值)。您需要在开始时将其初始化为零。如果没有 calloc,则必须手动遍历它,并在算法开始时将其初始化为零。calloc 可能可以更有效地为您执行此操作。

点赞,因为其他答案只提到了找到错误或快捷方式的优点(这也是很棒的信息)。它们没有提到(可能)calloc 的最初目的。有时您有一个分配内存并需要将数据初始化为零的算法。就这么简单。仅仅因为你最终会存储不同的值,并不意味着零不是正确的初始值。 - Merlyn Morgan-Graham
@Merlyn:对于“其余答案”的某些值 :-) - Alok Singhal
1
哦,抱歉。我没意识到你还在说话!;) - Merlyn Morgan-Graham

1

知道你分配的任何内容都被初始化为零是好的。许多错误都源于使用未初始化的内存的代码。另外,一些结构体/类的默认值可能为零,因此您无需在malloc之后更改所有值。

例如,使用malloc为其中一些指针配置了一个结构体。除非将它们设置为NULL,否则NULL检查并不总是有效。如果您使用calloc,则不需要为指针值执行额外的初始化步骤。


1
就像我在另一条评论中所说的那样:使用calloc使指针被设置为NULL是没有意义的,因为标准并没有保证所有位为零等于NULL - Alok Singhal

0

除了初始化变量的好处外,calloc还有助于跟踪错误。

如果您意外地使用未正确初始化的分配内存的一部分,则应用程序将始终以相同的方式失败。例如,从空指针引起的访问冲突。使用malloc时,内存具有随机值,这可能导致程序以随机方式失败。

随机故障非常难以跟踪,而calloc有助于避免这些问题。


0
首先,你不能calloc指针,至少如果你想遵循标准C的话是不行的。
其次,当你用所有零来覆盖成员时,错误只会被掩盖。更好的做法是有一个调试版本的malloc,它将内存初始化为总是崩溃的东西,比如0xCDCDCDCD。
然后当你看到访问冲突时,你就知道问题所在了。还有一个好处是有一个调试释放函数,它会用不同的模式清除内存,这样那些在释放后触摸内存的人会得到意外的惊喜。
在嵌入式系统上工作时,仅仅为了“确保”而使用calloc通常不是一个选项。你通常会一次性分配和填充,所以calloc只会使你重复触碰内存。

0
没有人谈到性能方面,所以我想我必须谈一下。如果你需要编写一个非常快的程序,malloc和“以防万一”集成的memset不是一个好方法。无论memset有多快,它总是太慢了。有时候你必须初始化一个向量或数组,所以真正的问题是控制你的时钟周期(即不浪费它们)。我曾经听过一句话:“你永远不应该意外地放弃性能”,这意味着从性能的角度来看,你必须始终知道为什么选择以某种方式实现代码(利弊分析以及在具体情况下如何权衡它们)。
如果你有一个将被填充字符串的缓冲区,初始化它可能是“好的”,但大多数人都会认为这完全是浪费时钟周期。如果你正在编写一个新的str*函数,你可能希望-出于调试目的-用通常不会出现的值填充缓冲区,但这将在发布时被删除。
正如其他人提到的,如果访问未初始化的变量,编译器会发出警告,因此我认为底线是,没有初始化“以防万一”的任何借口。

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