在一个简单的例子中,我试图了解printf
在C语言中的工作方式。我编写了以下程序:
#include "stdio.h"
int main(int argc, char const *argv[])
{
printf("Test %s\n", argv[1]);
return 0;
}
在二进制文件上运行objdump
,我发现Test %s\n
驻留在.rodata
objdump -sj .rodata bin
bin: file format elf64-x86-64
Contents of section .rodata:
08e0 01000200 54657374 2025730a 00 ....Test %s..
因此,格式化的打印似乎会执行从rodata
到其他地方的其他图案复制。
使用stare ./bin rr
编译并运行它之后,我注意到在实际写入之前有一个brk
系统调用。因此,使用
gdb catch syscall brk
gdb catch syscall write
显示在我的情况下,当前中断等于0x555555756000
,但是随后它设置为0x555555777000
。当write
出现格式化字符串时
x/s $rsi
0x555555756260: "Test rr\n"
位于“旧”和“新”之间。写入之后,程序退出。
问题: :为什么我们分配了这么多页面,为什么在发生写入系统调用后中断不返回上一页?是否有任何理由使用brk
而不是mmap
进行这种格式化?
答案 0 :(得分:1)
brk()
(和它的伴侣sbrk()
)是一种mmap()
,专门用于操纵堆大小。出于历史原因,它在那里,libc也可以直接使用mmap()
或mremap()
。
堆会随着分配的额外内存而扩展,例如使用malloc()
,这在libc内部发生,例如有足够的空间从格式字符串和参数或许多其他参数创建实际的字符串内部事物(即,将带缓冲的io与f *函数族一起使用时的输出缓冲)。
如果不再使用堆的某些部分,则通常不会自动释放,主要有两个原因:堆可能会碎片化,和/或未使用的堆不会低于证明该操作合理的某个阈值,因为可能很快会再次需要。
请注意:格式字符串本身当然不会从ro-section复制到堆中,这将是完全无用的。但是结果字符串(通常)是建立在堆上的。
答案 1 :(得分:1)
为什么我们要分配这么多页面?
使用系统调用的成本很高,因此该库目前的需求超出了您的需求,因为您很可能很快就会想要更多。在用户模式下管理内存的成本较低。这是一个粒度问题。
为什么中断不返回 上一次写系统调用后发生了吗?
再次,如果您很快就会要求更多的机会,为什么还要免费?
是否有理由使用
brk
而不是mmap
进行这种格式化?
这是一个选择问题,这取决于实现方式。
此外:您的问题更多是关于“内存分配策略”,而不是“了解printf”(即上下文)。