【问题标题】:malloc function on linuxlinux上的malloc函数
【发布时间】:2014-03-19 13:14:48
【问题描述】:

我最近在学习Linux C编程,有个问题困扰了我很久。问题是当我们使用malloc分配一些内存时,我们可以使用超出我们所需大小的地址,但是当我们访问的地址比我们需要的大,系统可能会杀死我们的进程。就像下面的代码:

int *p = malloc(10*sizeof(int));
*(p + 10) = 1;

当我们使用这个子句时,系统可能不会杀死我们的进程,但是当我们使用时:

*(p +10000) = 1;

系统可能会杀死我们的进程。 那么系统为什么会这样呢?

【问题讨论】:

  • 通过“系统杀死我们的进程”我假设你的意思是segmentation fault 正在发生?读取分配的内存是 C 未定义的行为,通常会导致段错误。
  • 如果你运气不好,*(p+10)=1; 可能会崩溃,这肯定会破坏你的记忆(因为最后一个有效索引是 9)。使用valgrind
  • 实际上 p+10 有效,因为 malloc 分配了 10 个 int 大小的内存空间,并且还留下了一个指向下一个要使用的内存地址的指针。但仅此而已。所以 *(p+10) 存在,*(p+11) 以后不存在。但是避免将数据强制转换到 malloc 指示的下一个内存槽,因为它们将被下一个 malloc 上的程序使用,并且,在最好的情况下,您将丢失信息(最坏的情况,程序会崩溃)
  • 您可能对stackoverflow.com/questions/20312340/… 感兴趣(即使从标题中看不出来)

标签: c linux


【解决方案1】:

实际上,您可以访问未分配的内存。

(有时)阻止您这样做的不是 C(它的设计理念是用户永远是对的),而是使用特殊 MMU(内存管理单元)硬件的操作系统。

此硬件不能(出于性能和成本原因)保护任何任意地址,而只能保护内存范围(页)。因此,非法(从程序员的角度来看)访问有时是可能的(如果它们在合法分配内存的同一内存页面上),其他非法访问(具有合法地址的外部页面)被发出页面错误的 MMU 阻止(分段违规)。

这显然并不意味着你可以访问未分配的内存,它只是解释了为什么你有时会忍受它。

当然,这一切仅适用于实际拥有 MMU 的平台。周围还有很多东西还没有,所以最好吸取教训;)。

【讨论】:

  • 这实际上是最好的答案。为什么没有被接受?
  • 答案解释得很好,这里有一个小补充:即使您请求(使用 sbrk)小于一页的大小,内核也总是以页为单位分配内存。因此,假设页面大小为 4096 字节,而您请求了 4000 字节,那么即使您在 4004 处访问内存,MMU 也会允许它。但是访问 4096 之后的任何内容都会产生非法地址错误。这样做的问题是您可能永远不知道特定硬件或操作系统上的页面大小。所以这样做总是很危险,但不是每次都致命。
【解决方案2】:

显然,您只能访问预先分配的内存。

所以你在做的基本上是:

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

在这里,您为 10 ints 分配内存。

*(p + 10) = 1;

这可以重写为(通过删除指针算术):

p[10] = 1;

现在,您可以清楚地看到第 10 项没有内存。所以理论上,你的代码应该已经在这里崩溃了。

但是,操作系统通常会决定分配更多内存您在 p 旁边分配了一块内存(在这种情况下,操作系统不会干预,即使您的代码无法按您希望的方式运行)。


让我举个例子:

char foo[4] = "foo";
char bar[4] = "bar";

现在(理论上,显然这取决于你的编译器做什么)如果你访问foo 的第 5 个字符,它实际上映射到bar 的第一个字符:

foo[5] = "c";
printf("%s %s\n", foo, bar); /* == foo car */

正如我所说,这取决于你的编译器,我只给你这个例子是因为它可以帮助你理解操作系统是如何分配内存的。


还有一件事:

NULL 指针 ((void *)0)“受保护”,操作系统保证当您尝试访问该地址的内容时它会失败。

【讨论】:

  • 您的回答不太正确/草率。您的程序可能能够访问您未分配的内存的原因一方面是 C 设计原则/标准以及现代计算机组织/管理内存访问权限的方式的结果。
  • C 有“未定义行为”的概念:C 标准定义了 C 在某些情况下的行为,但在其他情况下,标准只是声明:如果你这样做,任何事情都可能发生。访问您未分配的内存只是这些角落之一:C 故意不指定在这种情况下会发生什么 - 特别是它不需要阻止您的程序访问。因此,无论谁实现了您的 C 编译器,都可以随意终止您的程序、炸毁您的办公室,或者直接忽略它。
  • 在架构 /OS 级别上,内存通常以恒定大小的块(“段”)组织。只能为整个段授予访问权限。如果您的体系结构将内存组织为 16 字节的段,并且您为 1 字节分配了内存,则操作系统会在内部为您的程序分配一个 16 字节的整个段,并将其标记为可被您的进程读取/写入。这您的进程将能够访问 16 块“围绕”您的 1 字节分配。顺便说一句:如果你访问一个段,你会收到一个信号 'segv' - 分段错误。如果想了解更多,“MMU”将是一个很好的入门。
【解决方案3】:

如果你动态分配内存,你就是在堆上分配它。为此,您可以在内存块的开头分配一个指针。因此,您可以向该指针添加偏移量并访问特定的内存位置。如果此偏移量足够大,您的指针最终可能会指向堆外、数据段外或受保护的内存位置。这将导致分段错误 - 您可能正在访问用于其他用途的内存位置。

【讨论】:

  • 您唯一可以依赖的就是 c 标准所说的内容。并且 c 标准将访问未分配的内存定义为导致未定义的行为。可靠地获得分段错误会很棒。访问您未分配的内存不会可靠地触发 segv。内存被分成(通常是固定大小的)段。 MMU 授予对整个段的访问权限。因此,如果您分配的不是段的倍数,则有一些您从未分配过的内存块,但您仍然可以访问而不会导致 segv。
【解决方案4】:

您的问题不清楚,但我认为您是在问为什么您“可以”访问分配之外的元素。答案是你真的不能。不知道当你这样做时你会踩到什么——可能是另一个变量,甚至可能是你的代码。对于非常大的偏移量,您肯定会超出您“拥有”的系统内存,并且您的进程会被终止。

您似乎不知道自己在用 malloc() 做什么。回去再读一遍。

【讨论】:

  • 这太苛刻了。 @seabiscuitxf 只是放了一个示例虚拟代码,而不是某个东西的工作代码。这也是错误的,malloc 实际上留下了一个指向下一个内存空间的指针。所以 *(p+10) 被定义并且可以访问
  • 这不是太苛刻。他故意尝试访问分配之外的元素,这意味着他不知道自己在做什么。 p+10 可能会覆盖下一个堆块指针或其他会导致下一个 malloc() 损坏的东西。 p+10000可能不仅在堆之外,而且在他的可寻址内存空间之外。
  • 他只是好奇,一定是偶然发现的。您的最后一条评论非常有用,它说“您似乎不知道自己在用 malloc() 做什么。回去再读一遍。”不是
  • 这没有帮助,而且根据我所知道的“可以”的任何定义,实际上是错误的。无论您是通过查看 C 标准(在这种情况下味道不重要)来解决问题,还是 C 在真实机器上的实际实现,您都可以访问内存,或者至少,您可能能够访问。
猜你喜欢
  • 1970-01-01
  • 2018-07-13
  • 1970-01-01
  • 2017-07-30
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-12
相关资源
最近更新 更多