为什么malloc不malloc?

xmq68pz9  于 2023-10-15  发布在  其他
关注(0)|答案(3)|浏览(134)

下面是一个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请求nMB内存。
问题是,当我启动程序并在终端中输入free(在Linux中)时,结果是free所指示的已使用内存比所请求的内存小得多(如果你给给予一个大的n)。
这是我输入./a.out 1000后free的输出

$ 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循环中的句子修改为a[i] = 1;时,事情变得有趣起来。当我写入内存时,free和pmap告诉我实际上有1000MB用于物理内存。
为什么会这样?是不是从堆中“读”不会把新的页面带到物理内存中,而“写”会?我怀疑这与所谓的匿名文件有关。然而,关于它的讨论却很少。我在网上找不到有用的东西。
如果有人能给我给予一些帮助,我将不胜感激。
更新:对于那些好奇编译器是否进行优化的人,这里是汇编代码:

.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:
rryofs0p

rryofs0p1#

让我们首先指出,阅读malloc '的艾德内存而不首先对其进行初始化是未指定的行为。也许你应该使用calloc()代替。
在glibc(我假设您正在使用的库)中,malloc()(以及calloc()和家族中的其他库)通常使用brk系统调用来管理堆。但是,对于像您这样的非常大的分配,使用mmap(如果您希望更改malloc()开始使用mmap的阈值,请参阅mallopt())。
这两个系统调用都调用操作系统的虚拟内存管理器,它必须为您分配一些内存页(在x86上,常规页为4KiB)。但是,大多数操作系统都执行延迟分配。操作系统会将这些页面标记为已使用,但不会为它们分配任何物理内存。当您的代码引用内存时,它将出错,此时操作系统将实际将这些页面Map到物理内存,以便您可以使用它们。
通常情况下,操作系统会将所有分配的页面Map到单个物理零填充页面,这样就不会有读取开销。标签:Why malloc+memset is slower than calloc?
现在,如果您查看man 1 free,您将看到它始终显示物理内存使用情况。这与虚拟内存的怪癖无关。

js5cn81o

js5cn81o2#

这并没有什么奇怪的。
由于你的read没有做任何事情,它正在被编译器删除。

.L5:
        jmp     .L5

你必须强迫编译器做一些事情。你可以使用volatile数组或者放置一个内存屏障
范例:

((volatile int *)a)[i];
smdnsysy

smdnsysy3#

函数malloc()将分配一个内存块,接收到的内存块的内容没有初始化。这意味着它只是内存管理中的记录。
当你只是“读”它的时候,我想编译器会把它优化到什么都没有。您可以通过检查汇编代码来确认这一点。

相关问题