问题 malloc() - 它是否使用brk()或mmap()


c代码:

// program break mechanism
// TLPI exercise 7-1

#include <stdio.h>
#include <stdlib.h>

void program_break_test() {
    printf("%10p\n", sbrk(0));

    char *bl = malloc(1024 * 1024);
    printf("%x\n", sbrk(0));

    free(bl);
    printf("%x\n", sbrk(0));

}

int main(int argc, char **argv) {
    program_break_test();
    return 0;
}

编译以下代码时:

 printf("%10p\n", sbrk(0));

我收到警告提示:

format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘int’

问题1: 这是为什么?


在我之后 malloc(1024 * 1024),似乎程序休息没有改变。

这是输出:

9b12000
9b12000
9b12000

问题2: 进程在开始供将来使用时是否在堆上分配内存?或者编译器改变分配的时间点?否则,为什么?


[更新]摘要:brk()或mmap()

在查看TLPI并检查手册页(在TLPI的作者的帮助下)之后,现在我明白了 malloc() 决定使用 brk() 要么 mmap()如下:

mallopt() 可以设置参数来控制行为 malloc(),并且有一个名为的参数 M_MMAP_THRESHOLD, 一般来说:

  • 如果请求的内存小于它, brk() 将会被使用;
  • 如果请求的内存大于或等于它, mmap() 将会被使用;

参数的默认值为 128kb (在我的系统上),但在我的测试程序中,我使用1Mb,所以 mmap() 被选中,当我将请求的内存更改为32kb时,我看到了 brk() 会被使用。

书中提到TLPI第147页和第1035页,但我没有仔细阅读该部分。

参数的详细信息可以在手册页中找到 mallopt()


9316
2018-05-30 04:56


起源

#include <unistd.h>? - JS1
@ JS1是的,那解决了这个问题,你能解释一下,我是linux编程的新手...... - Eric Wang
你需要原型 sbrk() 在... unistd.h。如果没有原型,编译器会假定未知函数返回 int。 - JS1
@ JS1是的,我想 sbrk() 被宣布进入 stdlib.h, 谢谢! - Eric Wang
@EricWang:如果你用-Wall编译它会发出警告(假设你正在使用gcc或clang)。你应该总是用-Wall编译。 - rici


答案:


如果我们改变程序,看看在哪里 malloc记忆是:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void program_break_test() {
  printf("%10p\n", sbrk(0));

  char *bl = malloc(1024 * 1024);
  printf("%10p\n", sbrk(0));
  printf("malloc'd at: %10p\n", bl);

  free(bl);
  printf("%10p\n", sbrk(0));

}

int main(int argc, char **argv) {
  program_break_test();
  return 0;
}

这或许有点清楚 sbrk 不会改变。给我们的记忆 malloc 被映射到一个完全不同的位置。

你也可以使用 strace 在Linux上查看进行了哪些系统调用,并找出它 malloc 正在使用 mmap 执行分配。


11
2018-05-30 05:41



我发现有一个 THRESHOLD 控制是否使用 brk() 要么 mmap(),我在问题中更新了。 - Eric Wang


malloc 不仅限于使用 sbrk 分配内存。例如,它可能会使用 mmap 映射大 MAP_ANONYMOUS 记忆块;一般 mmap 将远离数据段分配虚拟地址。

还有其他可能性。尤其是, mmap作为标准库的核心部分,它本身并不局限于标准库函数;它可以使用特定于操作系统的接口。


2
2018-05-30 05:39





格式'%p'需要'void *'类型的参数,但参数2的类型为'int'

回答问题1:编译器告诉你参数应该是a void *,但你提供的是 int 代替。如果你花五秒钟阅读并理解错误,这应该是显而易见的。你有什么不明白的地方吗?如果是这样,请问一个更加详细的问题,让您感到困惑,而不是“为什么会这样?”......

应该发生类似的警告 printf("%x\n", sbrk(0));,按照 手册%x 预计将对应一个 unsigned 参数。另外,根据手册:

如果任何参数不是相应转换规范的正确类型,则行为未定义。

通常,我们应该努力编写在任何系统上以相同方式工作的程序。为此,我们需要建立一套规则。因此,会提供警告,告诉您违反规则并调用未定义的行为。尽管您的行为未定义,但您的代码仍然存在 可能有用 正如你所期望的那样 在你的系统上,此时此刻...但是,这不应该依赖,因为在将来的某个时候,您的计算机可能会获取更新,导致您的代码以微妙但破坏性的方式中断,或者可能无法在其他计算机上运行...或者可能只是选择 那个月的那个时候


问题2,3和4的答案:

进程在开始供将来使用时是否在堆上分配内存?

没有要求在标准C的规则中存在“堆”,所以这是“否”......至少,直到你告诉我们你正在使用哪个编译器/标准库。

或者编译器改变分配的时间点?

有可能。允许编译器执行甚至可能的优化 消除 你的分配,只要他们可以推断这样做是安全的(例如 可观察的行为 没有改变)。

否则,为什么?

好问题。

为什么我们有自由考虑某些事情的规则 未定义的行为 并允许它运行,即使在程序员的危险?优化。

为什么分配的内存是否存在问题  或者其他?你为什么要关心?只要你的记忆被分配,对吗?只要你能使用它,它就相当快。优化。

为什么编译器会执行优化?我会留下那个给你回答;)


1
2018-05-30 06:44