为什么使用malloc(1)可以存储4字节整数?

6
据我所知,malloc(x)返回一个x字节长的内存块。因此,要存储一个4字节的整数,我需要执行以下操作:
int *p = (int *)malloc(4);
*p = 100;

因为 sizeof(int) 对我而言返回的是 4。

然而,如果我执行以下操作:

int *p = (int *)malloc(1);
*p = 100;

看起来它的工作方式完全相同,没有存储价值的问题。

为什么用malloc()请求的内存量似乎不重要?一个4字节的整数不应该需要malloc(4)吗?


8
未定义行为是未定义的。如果将第二段代码循环执行数千次,你的程序可能会崩溃,这是理所当然的结果。 - Lundin
3
C语言没有边界检查。越界写入会导致未定义行为 - Some programmer dude
2
我也建议您阅读关于转换 malloc 结果的讨论。链接为:https://dev59.com/dHRB5IYBdhLWcg3wgHWr - Some programmer dude
2
你期望发生什么? - n. m.
1
@Lundin 可能不会,如果分配器有最小分配大小。 - M.M
你的程序行为未定义。它之所以能工作,仅是因为 malloc 通常返回对齐于最大数据类型的指针(通常为8或16字节),因此在大多数实现中,分配几个字节会分配至少8或16字节。 - Marian
8个回答

5
如果这在您的情况下有效,那只是偶然发生的,并不能保证有效。这是未定义行为(请参见此SO问题),任何事情都有可能发生。
你期望发生什么?你的程序崩溃了吗?
如果你调用mallocfree的频率更高,这种情况仍然可能发生。malloc通常会拿走比请求的内存多一些的字节,并使用额外的空间来管理(所有内存块的链接列表,内存块的大小)。如果你在分配的块之前或之后写入一些字节,那么你会很容易干扰内部管理结构,并且随后的mallocfree将会崩溃。
如果malloc在内部总是分配至少n个字节,那么只有当你访问第n+1个字节时,你的程序可能会崩溃。此外,操作系统通常只基于页面来保护内存。如果一个页面的大小为512字节,而你的malloc字节位于页面中间,则你的进程可能能够读写页面的其余部分,并且只有在访问下一个内存页面时才会崩溃。但请记住:即使这样也是未定义的行为。

3

malloc是C语言运行时库或操作系统内核中的所有内存块分配函数,它们都针对于内存访问和对象对齐进行了优化。

此外,malloc特别地在所分配的空间前面分配了一个隐藏的控制块来跟踪分配情况(所需空间,已分配空间等)。

malloc必须确保所分配的内存地址适合于任何存储对象的存储,这意味着所分配的内存块将以8、16、32甚至64或128字节边界开始,具体取决于处理器和硬件(例如一些特殊MMU)。该边界还取决于访问速度,某些处理器在不同的内存访问(1、2、4、8等字节)和地址边界下有不同的行为。这些约束条件驱动着malloc代码规范和分配器逻辑内存块的划分。

从实际角度考虑,假设我们使用X86处理器的分配器,它通常返回一个按照8字节边界对齐的块(32位代码),这对于int、float甚至double类型非常有用。为了实现这一点,malloc将可用的内存竞技场划分为“块”,它们是最小的分配空间。当你分配1个字节时,该函数至少分配一个块。最终,此块可以容纳一个整数,甚至是一个double类型,但这取决于具体实现,不能视为确定性的,因为在同一函数的未来版本中行为可能会更改。

现在,我希望您已经清楚了,因为您的代码似乎可以工作,请牢记这是未定义的行为,您必须保持它的状态。它现在可能能够工作,但在下一个版本中就不行了,它可能会在某些硬件上崩溃,而在另一个处理器或机器上则不会。


3
为此,我们应该了解malloc函数的内部工作原理。为了动态分配内存,每个操作系统都利用系统调用。我们可以使用这些系统调用来动态分配内存。这些系统调用在不同的操作系统中是不同的。
因此,一个操作系统的系统调用可能无法适用于另一个操作系统。而且,如果我们使用系统调用来动态分配内存,那么我们的程序将会依赖于平台。为了避免这种依赖性,我们使用malloc函数。现在,malloc函数的责任是基于操作系统进行适当的系统调用来动态分配内存。
因此,malloc本身会调用系统调用,这将是一个非常缓慢的过程,因为每次我们请求动态内存时,它都必须利用系统调用。为了避免这种情况,每当我们请求动态内存时,通常会分配额外的内存,以便下一次可以避免系统调用,并且可以使用先前分配的内存块的剩余部分。这就是为什么你的程序能正常工作,因为malloc正在分配额外的内存。

即使设置了 #pragma pack(1),仍然显示 4 字节。 - spiders.here

2

C编程语言赋予了你踢自己脚的能力。

它有意让程序员承担起应该知道自己在做什么的责任。总的来说,这是为了实现性能、可读性和可移植性。

你的代码行为是未定义的。如果你请求1个字节,那么只能得到一个可用的字节。操作系统和C运行库似乎给了你更多,但这只是一个奇怪的特殊情况。

在其他情况下,编译器可能会吃掉你的猫。

最后,在调用malloc时使用sizeof而不是硬编码int类型的大小:在许多系统上,sizeof(int)是2,4是常见的,所有大于1的值都被标准允许。在你的情况下,使用sizeof(int)sizeof(*p)都是可能的。有些人更喜欢后者,因为这样你就不需要在sizeof调用中硬编码变量类型,从而防止可能的变量类型更改。(请注意,sizeof(*p)是编译时可计算的,并使用静态类型信息;因此,如果你明白我的意思,它可以在p本身“存在”之前使用。)


2

看起来它完全一样,存储值没有问题。

您的代码存在未定义行为,因此无法确定其是否有效。要为整数分配内存,应该执行以下操作:

int *p;
p = malloc(sizeof (*p) ); //you can use sizeof(*p) as p is already declared and here you use the size of its content, which is actually the size of an int
if (p != NULL)
    *p = 100;

1
一种更好的替代方法是 int *p = malloc(sizeof *p) - Some programmer dude
1
sizeof *p,而不是 sizeof(p),这是指针的大小。 - mch
关于为什么您被允许在 sizeof 调用中引用 *p 的注意事项会很有帮助。这是一个好答案。 - Bathsheba
@Bathsheba 你是什么意思?我没有理解你鼓励我澄清的内容。 - Marievi
对于初学者来说,在声明 p 之前使用 sizeof (*p) 看起来很奇怪。 - Bathsheba

1
简单来说,当你为int类型分配1个字节时,其后面有3个字节实际上并没有被分配,但你仍然可以使用它们。你很幸运在测试期间这些字节没有被其他东西改变,并且它没有覆盖任何重要的内容(或者可能是)。因此,一旦这3个字节被其他内容需要时,就会导致错误——总是malloc正确的大小。

0
通常情况下,malloc的实现方式是分配不小于16字节的段大小内存。因此,当您需要4字节的内存时,malloc实际上会分配16字节的内存。然而,这种行为在C标准中没有描述,您不能依赖它。因此,这意味着您展示的程序具有未定义的行为。

0

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