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

4

17 回答 17

134

简单的逻辑:如果你不在合法的停车位停车,什么都不会发生,但偶尔你的车可能会被拖走,你可能会被罚款。而且,有时,当你试图找到你的汽车被拖到的地方时,你可能会被卡车碾过。

malloc为您提供尽可能多的合法停车位。您可以尝试将车停在其他地方,这似乎可行,但有时行不通。

对于诸如此类的问题,C FAQ 的内存分配部分是一个有用的参考参考。见7.3b

在相关的(幽默的)注释中,另请参阅ART花絮列表。

于 2009-08-06T20:00:14.997 回答
32

C 好心让你朝自己的脑袋开枪。您刚刚在堆上使用了随机内存。带来无法预料的后果。

免责声明:我最后一次真正的 C 编程是在大约 15 年前完成的。

于 2009-08-06T19:51:40.120 回答
29

让我给你一个类比为什么这个“有效”。

假设您需要绘制一幅画,因此您取出一张纸,将其平放在您的桌子上,然后开始绘制。

不幸的是,纸不够大,但你不在乎,或没有注意到,继续画你的画。

完成后,您退后一步,看看您的绘图,它看起来不错,完全符合您的意图,完全符合您绘制的方式。

直到有人走过来捡起他们在你拿到之前留在桌子上的纸。

现在有一块图画不见了。你在那个人的纸上画的那张。

此外,那个人现在在他的纸上还有你的画,可能会弄乱他想在纸上放的任何东西。

因此,虽然您的内存使用可能看起来有效,但这只是因为您的程序完成了。在运行一段时间的程序中留下这样的错误,我可以向你保证,你会得到奇怪的结果、崩溃等等。

C 就像在类固醇上构建的电锯。几乎没有什么是你做不到的。这也意味着你需要知道你在做什么,否则你会在不知不觉中看到穿过树并进入你的脚。

于 2009-08-06T20:06:11.867 回答
13

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

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

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

于 2009-08-06T19:52:57.653 回答
4

事实上,malloc没有为您的第三个整数分配足够的空间,但是您“幸运”并且您的程序没有崩溃。您只能确定 malloc 已准确分配了您要求的内容,仅此而已。换句话说,您的程序写入了一块未分配给它的内存。

所以 malloc 需要知道你需要的内存大小,因为它不知道你最终会用内存做什么,你计划写入内存的对象数量等等......

于 2009-08-06T19:51:51.010 回答
4

这一切都可以追溯到 C 让你在脚上开枪。仅仅因为你能做到这一点,并不意味着你应该这样做。除非您使用 malloc 专门分配它,否则绝对不能保证 p+3 处的值就是您放在那里的值。

于 2009-08-06T19:52:07.907 回答
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 放在连续的内存中。

于 2009-08-06T20:21:15.783 回答
2

内存表示为可以存储数字的可枚举的连续槽线。 malloc 函数将其中一些槽用于自己的跟踪信息,有时返回大于您需要的槽,以便稍后返回它们它不会被一小块无法使用的内存卡住。您的第三个 int 要么登陆 malloc 自己的数据,要么登陆返回块中剩余的空白空间,要么登陆 malloc 已从操作系统请求但尚未以其他方式分配给您的待处理内存区域。

于 2009-08-06T20:01:01.547 回答
2

根据平台, p[500] 也可能“工作”。

于 2009-08-06T20:09:38.160 回答
1

您要求两个整数的空间。p[3] 假设您有 4 个整数的空间!

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

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

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

这就像在餐厅要求座位一样。你可能会得到一张比你需要的更大的桌子。或者,您可能会与其他人坐在一张桌子旁。或者你可能会得到一张只有一个座位的桌子。只要您有一个座位,Malloc 就可以自由地做任何想做的事情。

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

于 2009-08-06T20:08:18.443 回答
1

使用 时malloc(),您将接受与运行时库的合同,在该合同中您同意要求您计划使用的内存量,并且同意将其提供给您。这是朋友之间的那种全口头的握手协议,这常常让人们陷入困境。当您访问超出分配范围的地址时,您就违反了您的承诺。

那时,您已经请求了标准所称的“未定义行为”,并且允许编译器和库做任何事情作为响应。即使看起来“正确”工作也是允许的。

不幸的是,它经常正常工作,因为这个错误很难编写测试用例来捕捉。对其进行测试的最佳方法包括替换malloc()为跟踪块大小限制并在每个机会都积极测试堆的健康状况的实现,或者使用像valgrind这样的工具从“外部”观察程序的行为并发现滥用缓冲存储器。理想情况下,这种误用会尽早失败并大声失败。

使用接近原始分配的元素通常会成功的一个原因是分配器通常会给出与对齐保证的方便倍数相关的块,并且这通常会导致在开始之前的一次分配结束时出现一些“备用”字节的下一个。然而,分配器通常将管理堆本身所需的关键信息存储在这些字节附近,因此超出分配可能会导致破坏malloc()本身需要成功进行第二次分配的数据。

编辑:*(p+2) OP 解决了与confounded against相关的附带问题,p[1]因此我编辑了我的答案以放弃这一点。

于 2009-08-06T20:10:10.313 回答
0

当您使用 * (p+3) 时,即使使用 2*sizeof(* p),您的寻址也会越界,因此您正在访问无效的内存块,非常适合 seg 错误。

您指定大小 b/c 否则,该函数不知道堆内存中有多大的块为该指针分配给您的程序。

于 2009-08-06T19:51:12.603 回答
0

因为 malloc() 在 BYTES 中分配。因此,如果要分配(例如)2 个整数,则必须以字节为单位指定 2 个整数的大小。整数的大小可以通过使用 sizeof(int) 找到,因此 2 个整数的字节大小为 2 * sizeof(int)。把这一切放在一起,你会得到:

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

注意:鉴于以上仅为两个整数分配空间,您在分配第三个整数时非常顽皮。你很幸运它没有崩溃。:)

于 2009-08-06T19:55:12.690 回答
0

因为 malloc 在堆上分配空间,这是您的程序使用的内存的一部分,它是动态分配的。然后,底层操作系统为您的程序提供请求的虚拟内存量(或者如果您最终遇到一些错误,这意味着您始终应该检查 malloc 的返回是否有错误条件),它使用一些映射到物理内存(即芯片)的虚拟内存聪明的魔法涉及复杂的事情,比如我们不想深入研究的分页,除非我们正在编写操作系统。

于 2009-08-06T20:02:00.797 回答
0

正如每个人所说,您正在写入实际未分配的内存,这意味着可能会发生某些事情来覆盖您的数据。为了演示这个问题,你可以尝试这样的事情:

int *p = malloc(2 * sizeof(int));
p[0] = 10; p[1] = 20; p[2] = 30;
int *q = malloc(2 * sizeof(int));
q[0] = 0; // This may or may not get written to p[2], overwriting your 30.

printf("%d", p[0]); // Correctly prints 10
printf("%d", p[1]); // Correctly prints 20
printf("%d", p[2]); // May print 30, or 0, or possibly something else entirely.

无法保证您的程序会在 p[2] 处为 q 分配空间。它实际上可能选择了一个完全不同的位置。但是对于这样一个简单的程序,这似乎很可能,并且如果它确实在 p[2] 所在的位置分配了 q,它将清楚地显示超出范围的错误。

于 2009-08-06T20:02:03.433 回答
0

分配给 malloc() 大小的原因是内存管理器跟踪系统上每个进程分配了多少空间。这些表帮助系统知道谁分配了多少空间,以及哪些地址是可用的。

其次,c 允许您随时写入 ram 的任何部分。内核可能会阻止您写入某些部分,从而导致保护错误,但没有什么可以阻止程序员尝试。

第三,很可能,第一次执行 malloc() 可能不会简单地为您的进程分配 8 个字节。这取决于实现,但内存管理器更有可能分配一个完整的页面供您使用,因为它更容易分配页面大小的块......然后后续的 malloc() 将进一步划分以前的 malloc( ) 的页面。

于 2009-08-06T20:12:25.427 回答
-3

做 :

int *p = malloc(2 * sizeof(*p)); // wrong (if type is something greater than a machine word)

[type] *p = malloc(2 * sizeof([type])); // right.
于 2009-08-06T21:37:31.040 回答