在C语言中使用malloc时为什么需要指定大小?

24

请看下面的代码:

int *p = malloc(2 * sizeof *p);

p[0] = 10;  //Using the two spaces I
p[1] = 20;  //allocated with malloc before.

p[2] = 30;  //Using another space that I didn't allocate for. 

printf("%d", *(p+1)); //Correctly prints 20
printf("%d", *(p+2)); //Also, correctly prints 30
                      //although I didn't allocate space for it

使用代码malloc(2 * sizeof *p),我为两个整数分配了空间,对吗?但是如果我在第三个位置添加一个int,它仍然可以正确分配和检索。

那么我的问题是:为什么在使用malloc时要指定大小


9
你选择了 p[3] 是因为它比 2 大,因此“超出范围”。你说得对,但要记住,你必须以零为起点来思考,所以实际上连 p[2] 本身也超出了范围。如果你已经为两个整数分配了空间,你可以使用 *p(或 p[0])和 *(p+1)(或 p[1])来获取整数,而不是 p[1] 和 p[2]。 - Tyler
2
除了所有答案所指出的问题外,如果malloc返回NULL(它是被允许的),那么你手上就有一个保证的段错误。在使用malloc之前,一定要检查其返回值。如果为NULL,则内存管理器拒绝为你分配更多内存(通常是因为你要求太多或者你已经用完了它愿意给你的所有内存)。 - Bob Somers
16
别担心,这不是一个愚蠢的问题,而是一个“我对低级编程概念还不熟悉”的问题。stepancheg需要学会不要咬新手。 - Tyler McHenry
2
似乎没有人花时间解释为什么malloc似乎会隐藏这个额外的空间给你。Malloc需要分配请求的空间+一点额外的空间来存储它自己关于已分配内存的元数据。对malloc分配的内存进行乱写的常见问题是破坏了malloc的元数据,导致后续的free和malloc操作失败。此外,根据具体实现,malloc可能会向操作系统请求大块内存,以便在调用另一个malloc时不必再次向操作系统请求(这只是简化的解释,但希望能够说明问题)。 - Falaina
1
@Falaina,那不完全正确。Malloc并不一定分配了问题中的额外空间。是的,malloc的工作方式就是这样,但它可能会在给你指针之前放置元数据*,或者放在其他地方。仅因为p [3]似乎有效并不意味着您已经找到了malloc的元数据。它可能是另一个变量的存储,或者是虚拟内存空间的完全未使用部分。 - Tyler McHenry
显示剩余6条评论
17个回答

134
简单的逻辑:如果你没有停在合法的停车位上,可能什么都不会发生,但有时你的车可能会被拖走,你可能会被罚款。而且有时,在你试图找到你的车被拖到哪里的路上,你可能会被卡车撞倒。 malloc 给你所要求的合法停车位数。你可以尝试在其他地方停车,它可能看起来有效,但有时它不会。
对于这样的问题,C FAQ 的内存分配部分是一个有用的参考资料。请参见7.3b
在相关(幽默)注释中,还可以查看ART提供的 失误列表

3
完美的比喻。你可以每天非法停车,也许不会发生什么坏事。但被拖走或罚款的可能性仍然存在,所以你不应该这么做。 - Tyler
1
你提供了一个好的解释,你会得到点赞。感谢你的解释。 - David Thornley
4
这个比喻不错,但你没有解释其中的含义。对于理解问题的人来说,这似乎是显而易见的...但如果他已经理解问题,就不会提出这个问题了。程序可能崩溃或者使用的内存可能被覆盖这些事实并不明显。请解释一下这个比喻背后发生的事情,这样就容易理解了,加一分。 - Beska
1
+1。非常好的比喻。而寻找内存损坏所浪费的时间则是对这种过失的罚款。 - Andrew Y
1
@AndrewY:如果你很幸运的话,“回到校园,憨豆先生”提供了更加现实的视角。当憨豆先生将一辆车推出被绳子隔离的停车位并自己使用时,他没有收到罚单。相反,军队计划中的展示坦克“越过任何障碍”的能力的演示在那个位置上使用了憨豆先生的车而不是他所移开的那辆车。 - supercat

32

C语言可以让你向自己的头部开枪。你刚刚使用了堆上的随机内存,后果无法预测。

声明:我最近一次真正使用C编程是大约15年前。


2
通常情况下,您分配的下一个变量将被覆盖,但如果您非常不幸,可能会撞到另一个程序的变量空间并影响某些随机内容。 - Ricket
我的意思是,如果在声明p之后,你还声明了int* q = malloc(sizeof(int));(一个包含一个元素的数组),那么很可能(但不保证)p[2] == q[0]。这也会导致程序可能继续运行,而且不会造成严重问题,然后突然出现p[2] != q[0]的情况,从而引发一个bug... 这些时有时无、难以预测的bug非常难以调试。 - Ricket

29

让我举个类比来说明为什么这种方式“可行”。

假设您需要画一幅图,于是您取出一张纸,在桌子上平放,开始画。

不幸的是,纸张不够大,但您并不在意或没有注意到,只是继续画您的图。

完成后,您退后一步,看着您的作品,它看起来很好,正如您打算的那样,并且与您画的方式完全相同。

直到有人来拿走他们在你之前放在桌子上的那张纸。

现在一部分图画就缺失了,就是那些您画在其他人纸上的部分。

此外,那个人现在在他的纸上有您绘画的一些部分,可能会影响他原本想要做的事情。

因此,尽管您的内存使用看起来可能有效,但它只能这样做是因为您的程序完成了。如果将这样的错误留在长时间运行的程序中,我可以保证您会得到奇怪的结果、崩溃等问题。

C语言就像是一把增强版的电锯,几乎没有什么是做不到的。这也意味着您需要知道自己在做什么,否则你会在你甚至没有察觉到之前就已经把树砍倒,并伤及自己的脚了。


这是一个非常好的比喻,因为它也回答了“为什么没有任何东西可以陷阱”的问题;一个人可以在纸张周围建立一个框架,以便笔不能超出它,但那肯定比拿一张纸做画要更费力。在某些环境中,框架的额外成本被认为是值得的。在其他环境中,使用笔的人被认为足够可信,不需要费心去做框架。 - supercat

13

你有(不)幸了。访问p[3]是未定义的,因为你没有为自己分配那块内存。读取/写入数组末尾之外的数据是C程序崩溃的一种神秘方式。

例如,这可能会更改通过malloc分配的某个其他变量中的某个值。这意味着它可能会在以后崩溃,而且很难找到覆盖数据的(无关)代码片段。

更糟糕的是,你可能会覆盖其他数据并且可能不会注意到。想象一下,这意外地覆盖了你欠某人的钱数 ;-)


8
很可能会覆盖有关堆中内容的信息,这意味着直到出现非常神秘的崩溃而没有明显原因之前,malloc()和free()可能会做出越来越奇怪的事情。请注意,此翻译仅供参考,具体语境可能需要更多背景信息才能准确表达。 - David Thornley

4

试试这个:

int main ( int argc, char *argv[] ) {
  int *p = malloc(2 * sizeof *p);
  int *q = malloc(sizeof *q);
  *q = 100;

  p[0] = 10;    p[1] = 20;    p[2] = 30;    p[3] = 40;
  p[4] = 50;    p[5] = 60;    p[6] = 70;


  printf("%d\n", *q);

  return 0;
}

在我的电脑上,它打印出:

50

这是因为您覆盖了为p分配的内存,并且损坏了q。

请注意,由于对齐限制,malloc可能不会将p和q放在连续的内存中。


4
事实上,malloc 没有为你的第三个整数分配足够的空间,但你“幸运”地避免了程序崩溃。你只能确保 malloc 正好分配了你所要求的内存大小,不能再多。换句话说,你的程序写入了一块未分配给它的内存。
因此,malloc 需要知道你需要的内存大小,因为它不知道你最终会对内存做什么,你计划向内存写入多少对象等等...

7
我认为这实际上是不幸的。 - bdonlan
1
你要知道,在某些系统上运行代码时,堆栈的行为会不同(当然必须是符合标准的)。客户可不想听到"在我的电脑上可以运行"这句话。 - sharptooth

4

这一切都要归功于C语言的灵活性,它让你可以自己给自己挖坑。但是,仅仅因为你能这样做,并不意味着你应该这样做。除非你使用malloc显式地对p+3进行内存分配,否则p+3处的值肯定不能保证与你放置的值相同。


2

内存被表示为一个可枚举的连续槽,可以在其中存储数字。malloc函数使用其中一些槽来存储自己的跟踪信息,并有时返回比您需要的更大的槽,以便在稍后返回它们时不会卡在无法使用的小块内存中。您的第三个int要么落在malloc自己的数据上,要么落在返回块中剩余的空间上,要么落在malloc从操作系统请求但尚未分配给您的挂起内存区域中。


2

根据平台的不同,p[500]可能也会“起作用”。


1
你正在请求两个整数的空间。p[3] 假设你有 4 个整数的空间!

===================

你需要告诉 malloc 你需要多少内存,因为它无法“猜测”你需要多少内存。

只要 malloc 返回至少你请求的内存量,它可以做任何想做的事情。

这就像在餐厅里要求一个座位。你可能会得到比你需要的更大的桌子。或者你可能会被安排坐在与其他人共用的桌子上。或者你可能会得到一个只有一个座位的桌子。只要你得到了你的单个座位,malloc 就可以自由地做任何想做的事情。

作为使用 malloc 的“合同”的一部分,你必须永远不引用超出你所请求的内存范围的内存,因为你只保证获得你所请求的数量。


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