我可以使用更多的内存,而不是使用malloc()分配多less内存,为什么?
char *cp = (char *) malloc(1); strcpy(cp, "123456789"); puts(cp);
在gcc(Linux)和Visual C ++ Express上,输出是“123456789”,这是否意味着当有空闲的内存时,我可以使用比malloc()
更多的分配吗?
为什么malloc(0)
不会导致运行时错误?
谢谢。
你提出了一个很好的问题,也许这会激起你对操作系统的兴趣。 你已经知道你已经设法实现这个代码,你通常不会期望这样做。 所以你永远不会做你想要做便携的代码。
更具体的说,这完全取决于你的操作系统和CPU体系结构,操作系统为你的程序分配“页面”的内存 – 通常这可能是4千字节。 操作系统是页面的监护人,并且会立即终止任何试图访问尚未被分配的页面的程序。
另一方面, malloc
不是一个操作系统函数,而是一个C库调用。 它可以以多种方式实施。 您对malloc
的调用可能会导致操作系统发出页面请求。 然后malloc
会决定给你一个指向该页面内的单个字节的指针。 当你从给定的位置写入内存的时候,你只是写在一个操作系统授予你的程序的“页面”上,这样操作系统就不会看到任何错误的行为。
当你继续调用malloc
来分配更多的内存时,真正的问题将会开始。 它最终会返回指向刚刚写过的位置的指针。 这被称为“缓冲区溢出”,当你写入合法的内存位置(从操作系统的angular度来看),但可能会覆盖内存的另一部分程序也将使用。
如果您继续了解这个主题,您将开始了解如何使用这种“缓冲区溢出”技术来利用程序,甚至是直接开始将汇编语言指令写入到将由另一个执行的内存区域你的程序的一部分。
当你到达这个阶段时,你将获得很多智慧。 但请道德,不要用它来破坏宇宙!
PS当我说上面的“操作系统”时,我的意思是“操作系统与特权CPU访问结合”。 如果进程尝试使用尚未分配给该进程的页面,则CPU和MMU(内存pipe理单元)会触发特定的中断或对操作系统的callback。 操作系统然后干净地closures您的应用程序,并允许系统继续运行。 在过去,在内存pipe理单元和特权CPU指令之前,你可以随时在内存中的任何地方写入内容,然后你的系统将完全受到内存写入的影响!
不,你得到未定义的行为 。 这意味着任何事情都可能发生,从它崩溃(耶)到它“工作”(嘘),它重新格式化您的硬盘驱动器,并填写文本文件,说“UB,UB,UB …”(笏)。
想知道之后会发生什么是没有意义的,因为它取决于你的编译器,平台,环境,时间,最喜欢的苏打水等等,所有这些都可以随心所欲地做任何他们想做的事情。
更具体地说,使用任何未分配的内存都是未定义的行为。 你从malloc(1)
得到一个字节 ,就是这样。
当你问malloc
1个字节,它可能会从操作系统得到1页(通常4KB)。 这个页面将被分配给调用进程,只要你不离开页面边界,你就不会有任何问题。
但请注意,这绝对是未定义的行为!
考虑使用malloc
时可能发生的以下(假设)例子:
-
malloc(1)
- 如果
malloc
内存不足,则会向操作系统请求更多内容。 它通常会收到一个页面。 假设它的大小是4KB,地址从0x1000开始 - 您的电话将返回给您的地址0x1000使用。 由于您询问了1个字节,因此如果您只使用地址0x1000,则会定义该行为 。
- 由于操作系统刚从地址0x1000开始为您的进程分配了4KB的内存,因此如果您从/向地址0x1000-0x1fff读取/写入内容,则不会投诉。 所以你可以高兴地这样做,但这是不确定的行为 。
- 假设你做另一个
malloc(1)
- 现在
malloc
还剩下一些内存,所以不需要向操作系统请求更多。 它可能会返回地址0x1001。 - 如果使用第一个
malloc
给出的地址写入超过1个字节,那么当您使用第二个malloc
的地址时将会遇到麻烦,因为您将覆盖数据。
所以问题是你肯定会从malloc获得1个字节, 但是 可能 malloc
内部有更多的内存分配给你的进程。
不,这意味着你的程序performance不好。 它写入到它不拥有的内存位置。
你得到未定义的行为 – 任何事情都可能发生。 不要这样做,也不要猜测它是否有效。 也许它腐败的记忆,你没有立即看到它。 只能在分配的块大小内访问内存。
您可能被允许使用,直到内存达到某些程序内存或其他应用程序很可能会崩溃以访问受保护的内存
这么多的回应,只有一个给出了正确的解释。 虽然页面大小,缓冲区溢出和未定义的行为故事是真实的(而且很重要),但它们并不完全回答原始问题。 实际上,任何理智的malloc
实现都会至less分配一个int
或void *
alignment要求的大小。 为什么,因为如果它只分配了1个字节,那么下一块内存就不会被alignment了。 总是有一些关于分配块的数据logging,这些数据结构几乎总是alignment到4的倍数。虽然某些体系结构可以访问未alignment的地址(x86)上的单词,但是确实有这样的惩罚,所以分配器实现者避免。 即使在slab分配器中,在1字节池中也没有意义,因为在实践中小规模的分配是很less见的。 所以很可能你的malloc'd字节有4或8个字节的真实空间(这并不意味着你可以使用这个'function',这是错误的)。
编辑:此外,大多数malloc
保留更大的块要求,以避免许多复制操作时调用realloc
。 作为一个testing,你可以尝试在循环中使用realloc
,并在比较返回的指针的时候,你会看到它只在一定的阈值之后才会改变。
你在那里很幸运 您正在写信给您不属于自己的地点,导致未定义的行为。
在大多数平台上,你不能只分配一个字节。 malloc通常还有一些内务处理来记住分配的内存量。 这产生了一个事实,即通常“分配”四舍五入到接下来的4或8个字节的内存。 但这不是一个明确的行为。
如果你使用更多的字节,你会非常喜欢访问冲突。
为了回答你的第二个问题,该标准明确要求malloc(0)
是合法的。 返回值是依赖于实现的,可以是NULL
或是一个常规的内存地址。 在任何一种情况下,您都可以(也应该)合法地在返回值上free
完成。 即使非NULL
,也不能访问该地址的数据。
malloc分配你在堆中请求的内存量,然后返回一个指向void(void *)的指针,该指针可以被转换为任何你想要的。
程序员有责任只使用已分配的内存。 写作(甚至在受保护的环境中阅读)你不应该在执行时引起各种随机问题。 如果你很幸运,你的程序会立即崩溃,并且很容易findbug并修复它。 如果你不幸运,它会随机崩溃或产生意想不到的行为。
对于墨菲定律 , “任何可能出错,都会出错”的结果是“在适当的时候出错,产生最大的损失” 。 这是可悲的事实。 防止这种情况的唯一方法就是避免使用这种语言,你可以这样做。
现代语言不允许程序员在内存中进行写操作(至less是进行标准编程)。 这就是Java如何引起了很大的关注。 我更喜欢C ++到C.你仍然可以使用指针造成损害,但它不太可能。 这就是智能指针如此受欢迎的原因。
为了解决这些问题,malloc库的debugging版本可以很方便。 您需要定期调用一个检查函数来检测内存是否损坏。 当我在工作中使用C / C ++的时候,我们使用了Rational Purify ,它实际上replace了标准的malloc(C ++中的new)和free(C ++中的删除),它能够返回相当准确的报告,这是不应该的。 但是,你永远无法确定你的代码没有任何错误。 如果你遇到的情况极less发生,当你执行这个程序的时候,你可能不会在这种情况下发生。 在最敏感的数据中,最终会在最繁忙的一天的生产中发生(根据墨菲定律);-)
这可能是因为你处于debugging模式,其中一个对malloc的调用实际上会调用_malloc_dbg 。 debugging版本将分配更多的空间,比你所要求的缓冲区溢出。 我想,如果你在发布模式下运行,你可能会(希望)会崩溃。
你应该使用新的和删除运算符在c + + …一个安全的指针来控制操作没有达到分配的数组的限制…
没有“C运行时”。 C是荣耀的汇编。 它会很高兴地让你遍布整个地址空间,随心所欲地做任何事情,这就是为什么它是编写操作系统内核的首选语言。 您的程序是堆错误的一个例子,这是一个常见的安全漏洞。 如果你写了一个足够长的string到这个地址,你最终会超出堆的末尾,并得到一个分段错误,但不是先覆盖了许多其他重要的事情之前。
当malloc()在其储备池中没有足够的可用内存来满足分配时,它会以至less4kb的块的forms抓取内核中的页面,而且通常要大得多,所以你可能会写入保留的, malloc()编辑空间,当你最初超出你的分配范围,这就是为什么你的testing案例总是工作。 实际上,遵循分配地址和大小是完全自愿的,所以你可以为一个指针指定一个随机地址,而不用调用malloc(),并开始把它作为一个string来处理,只要这个随机地址恰好在像堆或堆栈这样的可写内存段,一切似乎都可以工作,至less在你尝试使用任何你正在损坏的内存之前。
strcpy()不检查它正在写入的内存是否被分配。 它只取目标地址,并逐个字符地写入源字符,直到达到“\ 0”。 所以,如果分配的目标内存比源小,你只是写了内存。 这是一个危险的错误,因为它很难追查。
puts()将string写入,直到达到“\ 0”。
我的猜测是,malloc(0)只返回NULL,不会导致运行时错误。