提问人:Black Pan 提问时间:10/11/2023 最后编辑:Black Pan 更新时间:10/12/2023 访问量:179
为什么 malloc 不 malloc?
Why malloc doesn't malloc?
问:
这里有一个C程序来介绍这个问题。
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[]) {
if(argc != 2) {
printf("Provide a number to indicate the number of bytes (in Mega)\n");
exit(8);
}
int num = atoi(argv[1]);
size_t max = num * pow(2, 18);
printf("declared %ld ints\n", max);
int *a = malloc(max * sizeof(int));
while(1) {
for(size_t i = 0; i < max; i++) {
printf("%d",a[i]);
}
}
return 0;
}
该程序执行简单操作。它从命令行读取一个数字,比如 n,然后通过 malloc 请求 n MB 内存。
问题是当我启动程序并在终端中键入 free(在 Linux 中)时,事实证明 free 指示的已用内存比请求的内存小得多(如果您给出一个大的 n)。
这是我输入后 free 的输出./a.out 1000
$ free -h
total used free shared buff/cache available
Mem: 12Gi 649Mi 11Gi 0.0Ki 320Mi 11Gi
Swap: 4.0Gi 0B 4.0Gi
以及来自 pmap 的更详细的输出
$ pmap 18414 -x
18414: ./a.out 1000
Address Kbytes RSS Dirty Mode Mapping
00005642164b8000 4 4 0 r---- a.out
00005642164b9000 4 4 0 r-x-- a.out
00005642164ba000 4 4 0 r---- a.out
00005642164bb000 4 4 4 r---- a.out
00005642164bc000 4 4 4 rw--- a.out
0000564218248000 132 4 4 rw--- [ anon ]
00007fa6d1b9a000 1024016 12 12 rw--- [ anon ]
00007fa71039e000 160 160 0 r---- libc.so.6
00007fa7103c6000 1620 852 0 r-x-- libc.so.6
00007fa71055b000 352 148 0 r---- libc.so.6
00007fa7105b3000 16 16 16 r---- libc.so.6
00007fa7105b7000 8 8 8 rw--- libc.so.6
00007fa7105b9000 52 20 20 rw--- [ anon ]
00007fa7105d0000 8 4 4 rw--- [ anon ]
00007fa7105d2000 8 8 0 r---- ld-linux-x86-64.so.2
00007fa7105d4000 168 168 0 r-x-- ld-linux-x86-64.so.2
00007fa7105fe000 44 44 0 r---- ld-linux-x86-64.so.2
00007fa71060a000 8 8 8 r---- ld-linux-x86-64.so.2
00007fa71060c000 8 8 8 rw--- ld-linux-x86-64.so.2
00007ffdc8b06000 136 12 12 rw--- [ stack ]
00007ffdc8b9b000 16 0 0 r---- [ anon ]
00007ffdc8b9f000 4 4 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 1026776 1496 100
当我将 for 循环中的句子修改为 .当我写入内存时,free 和 pmap 告诉我物理内存中实际上使用了 1000MB。a[i] = 1;
为什么会这样?是不是” 从堆中读取“不会将新页面带入物理内存,但”写入“会吗?我怀疑这与所谓的匿名文件有关。但是,关于它的讨论很少。我未能在网络上找到有用的东西。
如果有人能提供一些帮助,我将不胜感激。
更新: 对于那些对编译器是否进行优化感到好奇的人,这里是汇编代码:
.file "memory-user.c"
.text
.section .rodata
.align 8
.LC0:
.string "Provide a number to indicate the number of bytes (in Mega)"
.LC3:
.string "declared %ld ints\n"
.LC4:
.string "%d"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $48, %rsp
movl %edi, -36(%rbp)
movq %rsi, -48(%rbp)
cmpl $2, -36(%rbp)
je .L2
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $8, %edi
call exit@PLT
.L2:
movq -48(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, -28(%rbp)
pxor %xmm1, %xmm1
cvtsi2sdl -28(%rbp), %xmm1
movsd .LC1(%rip), %xmm0
mulsd %xmm1, %xmm0
comisd .LC2(%rip), %xmm0
jnb .L3
cvttsd2siq %xmm0, %rax
movq %rax, -16(%rbp)
jmp .L4
.L3:
movsd .LC2(%rip), %xmm1
subsd %xmm1, %xmm0
cvttsd2siq %xmm0, %rax
movq %rax, -16(%rbp)
movabsq $-9223372036854775808, %rax
xorq %rax, -16(%rbp)
.L4:
movq -16(%rbp), %rax
movq %rax, -16(%rbp)
call getpid@PLT
movl %eax, %edx
movq -16(%rbp), %rax
movq %rax, %rsi
leaq .LC3(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movq -16(%rbp), %rax
movl $4, %esi
movq %rax, %rdi
call calloc@PLT
movq %rax, -8(%rbp)
.L7:
movq $0, -24(%rbp)
jmp .L5
.L6:
movq -24(%rbp), %rax
leaq 0(,%rax,4), %rdx
movq -8(%rbp), %rax
addq %rdx, %rax
movl (%rax), %eax
movl %eax, %esi
leaq .LC4(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
addq $1, -24(%rbp)
.L5:
movq -24(%rbp), %rax
cmpq -16(%rbp), %rax
jb .L6
jmp .L7
.cfi_endproc
.LFE6:
.size main, .-main
.section .rodata
.align 8
.LC1:
.long 0
.long 1091567616
.align 8
.LC2:
.long 0
.long 1138753536
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
答:
函数 malloc() 将分配一个内存块,接收到的内存块的内容不会初始化。这意味着它只是内存管理中的一条记录。
当你只是“读取”它时,我认为编译器会将其优化到零。您可以通过检查汇编代码来确认这一点。
评论
这没什么奇怪的。
由于您的读取不执行任何操作,因此编译器正在将其删除。
.L5:
jmp .L5
您必须强制编译器执行某些操作。您可以使用易失性数组或放置内存屏障
例:
((volatile int *)a)[i];
让我们首先指出,在不先初始化内存的情况下读取内存是未指定的行为。也许你应该改用。malloc
calloc()
在 glibc(我假设您正在使用的库)中(以及该系列中的其他人)通常使用 brk
系统调用来管理堆。但是,对于像您这样的非常大的分配,将使用 mmap
(如果您希望更改开始使用 的阈值,请参阅 mallopt()
)。malloc()
calloc()
malloc()
mmap
这两个系统调用都会调用操作系统的虚拟内存管理器,该管理器必须为您分配一些内存页(在 x86 上,常规页为 4KiB)。但是,大多数操作系统都执行延迟分配。OS 会将这些页面标记为已使用,但不会为它们分配任何物理内存。当您的代码引用内存时,它会出错,这时操作系统会将这些页面实际映射到物理内存,以便您可以使用它们。
通常,操作系统实际上会将所有分配的页面映射到单个物理零填充页面,因此没有读取开销。Смотритетакже: 为什么 malloc+memset 比 calloc 慢?
现在,如果你看一下 man 1 free
,你会看到它总是显示物理内存使用情况。它与虚拟内存的怪癖无关。
评论
int
评论
a[i];