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的存储