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()
。
如果我们改变程序,看看在哪里 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
执行分配。
malloc
不仅限于使用 sbrk
分配内存。例如,它可能会使用 mmap
映射大 MAP_ANONYMOUS
记忆块;一般 mmap
将远离数据段分配虚拟地址。
还有其他可能性。尤其是, mmap
作为标准库的核心部分,它本身并不局限于标准库函数;它可以使用特定于操作系统的接口。
格式'%p'需要'void *'类型的参数,但参数2的类型为'int'
回答问题1:编译器告诉你参数应该是a void *
,但你提供的是 int
代替。如果你花五秒钟阅读并理解错误,这应该是显而易见的。你有什么不明白的地方吗?如果是这样,请问一个更加详细的问题,让您感到困惑,而不是“为什么会这样?”......
应该发生类似的警告 printf("%x\n", sbrk(0));
,按照 手册, %x
预计将对应一个 unsigned
参数。另外,根据手册:
如果任何参数不是相应转换规范的正确类型,则行为未定义。
通常,我们应该努力编写在任何系统上以相同方式工作的程序。为此,我们需要建立一套规则。因此,会提供警告,告诉您违反规则并调用未定义的行为。尽管您的行为未定义,但您的代码仍然存在 可能有用 正如你所期望的那样 在你的系统上,此时此刻...但是,这不应该依赖,因为在将来的某个时候,您的计算机可能会获取更新,导致您的代码以微妙但破坏性的方式中断,或者可能无法在其他计算机上运行...或者可能只是选择 那个月的那个时候。
问题2,3和4的答案:
进程在开始供将来使用时是否在堆上分配内存?
没有要求在标准C的规则中存在“堆”,所以这是“否”......至少,直到你告诉我们你正在使用哪个编译器/标准库。
或者编译器改变分配的时间点?
有可能。允许编译器执行甚至可能的优化 消除 你的分配,只要他们可以推断这样做是安全的(例如 可观察的行为 没有改变)。
否则,为什么?
好问题。
为什么我们有自由考虑某些事情的规则 未定义的行为 并允许它运行,即使在程序员的危险?优化。
为什么分配的内存是否存在问题 堆 或者其他?你为什么要关心?只要你的记忆被分配,对吗?只要你能使用它,它就相当快。优化。
为什么编译器会执行优化?我会留下那个给你回答;)