C 解释编译后的代码结构和静态分配

C 解释编译后的代码结构和静态分配,c,compilation,C,Compilation,我在看char*C=“thomas”和字符c[]=“托马斯”。我在这里看到了有关这方面的问题,在试图理解答案的同时,我想通过查看大会来检查我是否正确。于是就产生了几个问题 以下是我的想法: char*c=…:字符与代码一起分配到静态内存中的某个位置(从程序的角度来看是只读的)。这就是为什么它应该被标记为const。字符串可以打印,但不能修改 charc[]=…:与1相同。除了调用函数时,字符被复制到堆栈上的数组中,因此可以对其进行修改等 我想检查一下,所以我做了这个C代码: #include &

我在看
char*C=“thomas”
字符c[]=“托马斯”。我在这里看到了有关这方面的问题,在试图理解答案的同时,我想通过查看大会来检查我是否正确。于是就产生了几个问题

以下是我的想法:

  • char*c=…
    :字符与代码一起分配到静态内存中的某个位置(从程序的角度来看是只读的)。这就是为什么它应该被标记为
    const
    。字符串可以打印,但不能修改

  • charc[]=…
    :与1相同。除了调用函数时,字符被复制到堆栈上的数组中,因此可以对其进行修改等

  • 我想检查一下,所以我做了这个C代码:

    #include <stdio.h>
    
    int     main(){
      char c [] = "thomas blabljbflkjbsdflkjbds";
      printf("%s\n", c);
    }
    
    #包括
    int main(){
    字符c[]=“thomas blabljbflkjbsdflkjbds”;
    printf(“%s\n”,c);
    }
    
    查看生成的程序集:

       0x400564 <main>:    push   rbp
       0x400565 <main+1>:    mov    rbp,rsp
       0x400568 <main+4>:    sub    rsp,0x30
       0x40056c <main+8>:    mov    rax,QWORD PTR fs:0x28
       0x400575 <main+17>:    mov    QWORD PTR [rbp-0x8],rax
       0x400579 <main+21>:    xor    eax,eax
       0x40057b <main+23>:    mov    DWORD PTR [rbp-0x30],0x6978616d
       0x400582 <main+30>:    mov    DWORD PTR [rbp-0x2c],0x6220656d
       0x400589 <main+37>:    mov    DWORD PTR [rbp-0x28],0x6c62616c
       0x400590 <main+44>:    mov    DWORD PTR [rbp-0x24],0x6c66626a
       0x400597 <main+51>:    mov    DWORD PTR [rbp-0x20],0x73626a6b
       0x40059e <main+58>:    mov    DWORD PTR [rbp-0x1c],0x6b6c6664
       0x4005a5 <main+65>:    mov    DWORD PTR [rbp-0x18],0x7364626a
       0x4005ac <main+72>:    mov    BYTE PTR [rbp-0x14],0x0
       0x4005b0 <main+76>:    lea    rax,[rbp-0x30]
       0x4005b4 <main+80>:    mov    rdi,rax
       0x4005b7 <main+83>:    call   0x400450 <puts@plt>
       0x4005bc <main+88>:    mov    rdx,QWORD PTR [rbp-0x8]
       0x4005c0 <main+92>:    xor    rdx,QWORD PTR fs:0x28
       0x4005c9 <main+101>:    je     0x4005d0 <main+108>
    
    0x400564:推送rbp
    0x400565:mov rbp,rsp
    0x400568:子rsp,0x30
    0x40056c:mov rax,QWORD PTR fs:0x28
    0x400575:mov QWORD PTR[rbp-0x8],rax
    0x400579:xor eax,eax
    0x40057b:mov DWORD PTR[rbp-0x30],0x6978616d
    0x400582:mov DWORD PTR[rbp-0x2c],0x6220656d
    0x400589:mov DWORD PTR[rbp-0x28],0x6c62616c
    0x400590:mov DWORD PTR[rbp-0x24],0x6c66626a
    0x400597:mov DWORD PTR[rbp-0x20],0x73626a6b
    0x40059e:mov DWORD PTR[rbp-0x1c],0x6b6c6664
    0x4005a5:mov DWORD PTR[rbp-0x18],0x7364626a
    0x4005ac:mov字节PTR[rbp-0x14],0x0
    0x4005b0:lea-rax[rbp-0x30]
    0x4005b4:mov rdi,rax
    0x4005b7:调用0x400450
    0x4005bc:mov rdx,QWORD PTR[rbp-0x8]
    0x4005c0:xor rdx,QWORD PTR fs:0x28
    0x4005c9:je 0x4005d0
    
    所以字符被复制到堆栈中,这就是我的想法

    问题:

  • 字符按字节存储在地址
    0x6978616d、0x6220656d
    等处。为什么它们不在数组中连续分配?编译器的简单优化

  • 解释为什么
    char*
    的行为不像数组,为什么
    c[10]
    不是字符串的第11个字符。然而,这并不能解释为什么
  • char*c=“thomas blabljbflkjbsdflkjbds”; printf(“%s\n”,c)

    工作。(请注意[]->*)。我猜,
    printf
    一个字符一个字符地读取,直到达到0,所以只知道c
    (即e&c[0])
    它如何访问
    c[10]
    ?(因为不连续,而且这次字符没有复制到堆栈上的数组)


    我希望我很清楚,如果你问/不明白一点,我可以重新表述。谢谢

    1:
    0x6978616d
    0x6220656d
    不是地址,而是字符串中的数据。当从十六进制转换为ascii时,
    0x6978616d
    =
    moht
    0x6220656d
    =
    BSA


    2:在函数调用中使用时,数组会衰减为指针。因此,无论
    c
    是数组还是指针,
    printf
    都将收到指向char的指针。

    1:
    0x6978616d
    0x6220656d
    不是地址,而是字符串中的数据。当从十六进制转换为ascii时,
    0x6978616d
    =
    moht
    0x6220656d
    =
    BSA


    2:在函数调用中使用时,数组会衰减为指针。因此,无论
    c
    是数组还是指针,
    printf
    都会收到指向char的指针。

    编译器实际上可能会选择将字符数组初始化编译为只读存储器的副本,但正如Klas所建议的,在您的示例中不会发生这种情况

    下面是一个代码示例(使用gcc)。将
    STR
    的定义更改为不同长度的字符串并查看汇编输出中的差异可能会有所启发

    /* 99 characters */
    #define STR "123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789"
    
    void observe(const char *);
    
    void test1() {
        char *str = STR;
        observe(str);
    }
    void test2() {
        char str[] = STR;
        observe(str);
    }
    
    大会:

        .section    .rodata.str1.4,"aMS",@progbits,1
        .align 4
    .LC0:
        .string "123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789"
    
        .text
    test2:
        pushl   %ebp
        movl    $25, %ecx
        movl    %esp, %ebp
        subl    $136, %esp
        movl    %esi, -8(%ebp)
        movl    $.LC0, %esi
        movl    %edi, -4(%ebp)
        leal    -108(%ebp), %edi
        rep movsl
        leal    -108(%ebp), %eax
        movl    %eax, (%esp)
        call    observe
        movl    -8(%ebp), %esi
        movl    -4(%ebp), %edi
        movl    %ebp, %esp
        popl    %ebp
        ret
    
    test1:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $.LC0, (%esp)
        call    observe
        leave
        ret
    

    编译器实际上可能会选择将字符数组初始化编译为只读存储器中的副本,但正如Klas所建议的,在您的示例中不会发生这种情况

    下面是一个代码示例(使用gcc)。将
    STR
    的定义更改为不同长度的字符串并查看汇编输出中的差异可能会有所启发

    /* 99 characters */
    #define STR "123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789"
    
    void observe(const char *);
    
    void test1() {
        char *str = STR;
        observe(str);
    }
    void test2() {
        char str[] = STR;
        observe(str);
    }
    
    大会:

        .section    .rodata.str1.4,"aMS",@progbits,1
        .align 4
    .LC0:
        .string "123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789"
    
        .text
    test2:
        pushl   %ebp
        movl    $25, %ecx
        movl    %esp, %ebp
        subl    $136, %esp
        movl    %esi, -8(%ebp)
        movl    $.LC0, %esi
        movl    %edi, -4(%ebp)
        leal    -108(%ebp), %edi
        rep movsl
        leal    -108(%ebp), %eax
        movl    %eax, (%esp)
        call    observe
        movl    -8(%ebp), %esi
        movl    -4(%ebp), %edi
        movl    %ebp, %esp
        popl    %ebp
        ret
    
    test1:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $.LC0, (%esp)
        call    observe
        leave
        ret
    

    我不太懂汇编,你能用英语解释一下你所看到的差异吗?感谢感兴趣的位是加载只读存储器地址的
    movl$.LC0,%esi
    ,以及块复制指令
    rep movsl
    。将其与您帖子中的程序集进行比较:根本没有复制,只是将即时值存储到堆栈中。非常有趣,我开始明白您的意思(除了块复制:将哪个块复制到哪里?)。如果编译器没有复制test2堆栈上的字符,为什么您认为它会在test2堆栈上保留0x108字节左右?我想我没有说得足够清楚:
    test2
    的程序集确实会将字符串从只读内存(即
    .LC0
    )复制到堆栈上。好吧,我还想了一件事:当只使用f(const char*)时编译器会认为不需要复制,并直接给出指向静态只读内存的指针。太糟糕了,这没有发生,但我现在也明白了你的观点:字节不是在代码中传递的,而是在静态内存中如果在不同的地方使用,字符串很大,可以保存一些代码。我不太懂汇编,你能用英语解释你看到的差异吗?感谢感兴趣的位是加载只读存储器地址的
    movl$.LC0,%esi
    ,以及块复制指令
    rep movsl
    。与你帖子中的程序集相比:根本没有拷贝,只有immedia的存储