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

时间:2013-12-17 11:36:12

标签: c compilation

我正在研究char* c = "thomas";char c[] = "thomas";之间的差异。我在这里看到了有关这方面的问题,在尝试理解答案时,我想通过查看装配来检查我是对的。并且出现了一些问题。

以下是我的想法:

  1. char* c = ...:字符分配在静态内存的某处(从程序的角度来看),以及代码。这就是它应该被标记为const的原因。字符串可以打印但不能修改。

  2. char c[] = ...:与1.相同,除了在调用函数时,字符被复制到堆栈中的数组中,因此可以修改等等。

  3. 我想检查这个,所以我做了这个C代码:

    #include <stdio.h>
    
    int     main(){
      char 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>
    

    所以字符被复制到堆栈中,这就是我的想法。

    问题:

    1. 字符按地址0x6978616d, 0x6220656d的字节存储,依此类推。为什么它们不是在数组中连续分配的?简单优化编译器?

      1. 解释了为什么char*的行为不像数组,为什么c[10]不是字符串的第11个字符。 然而它没有解释原因
      2. char * c =“thomas blabljbflkjbsdflkjbds”; printf(“%s \ n”,c);

        的工作原理。 (注意[] - &gt; *)。我想printf按字符读取字符直到达到0,所以只知道c (i.e &c[0])它如何访问c[10]? (因为不连续而且这个时间字符没有复制到堆栈上的数组这一事实)

        我希望我很清楚,如果你要求/不理解一点,我可以重新制定。感谢

2 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:1)

编译器实际上可能会选择将字符数组初始化编译为只读存储的副本,但正如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