在 C 中,char *p; 是指向 char 的指针的声明,当您不初始化变量时,编译器不会进行内存分配。
下面的代码只是声明了一个指针,它和int *i;没有什么不同。你是在告诉你的编译器p 指向一个存储char 的内存地址。
char *p; /* pointe to a char, that's it */
而在下面的代码 sn-p 中,您初始化了内存(只读)。与上面的示例类似,p 仍然是指向char 的指针,但p 之后的内存位置(即p+1,p+2,...)现在也分配给字符。这是由您的编译器完成的,但分配是只读的。
char *p = "Hello"; /* pointer to a char but the memory address */
/* it is pointing to has been initialised */
查看您在 OP 中的代码 sn-p
#include <stdio.h>
int main()
{
char *p;
p[0]='a';
p[1]='b';
p[2]='c';
p[3]='d';
p[4]='\0';
}
虽然直觉可能会暗示,这
char *p;
p[0]='a'; /* error here */
不一样
char *p, c='a';
p=&c;
前者要求您有一个已初始化(分配)的内存。在 C 中,您只能在声明字符指针时为其分配一个常量值。即
char *p="a"; /* valid operation */
如果指向 char 的指针在声明时未初始化,则以后不能再进行初始化。从那时起,您可以执行的唯一有效的赋值操作是将 char 变量的地址分配给指针。就是这样。
让我们看看您的编译器如何解释两个操作并从那里得出结论。拿这个玩具程序
int main() {
char c, *p, *str = "Hello";
c = 'a';
p = &c;
p[0] = 'a';
}
没有任何优化,clang 发出下面的代码
main: # @main
push rbp ; function prologu
mov rbp, rsp ; function prologu
xor eax, eax
movabs rcx, offset .L.str ; *str = "Hello"
mov qword ptr [rbp - 24], rcx
mov byte ptr [rbp - 1], 97 ; c = 'a'
lea rcx, [rbp - 1] ; p = &c
mov qword ptr [rbp - 16], rcx ; p = &c
mov rcx, qword ptr [rbp - 16] ; p[0]='a'
mov byte ptr [rcx], 97 ; p[0]='a'
pop rbp
ret
.L.str:
.asciz "Hello"
LEA instruction 将第二个操作数的有效地址加载到第一个操作数中。在上面的代码中,内存位置rbp-1 是为变量c 保留的,因此它的值是97,这是a 的ASCII 码。而位置rbp-16 是指针p。
下面的代码
int main() {
char c, *p, *str = "Hello";
c = 'a';
p = &c;
p=malloc(1); /* added a malloc() here */
p[0] = 'a';
}
下面是汇编代码
main: # @main
push rbp ; function prologue
mov rbp, rsp ; function prologu
sub rsp, 32 ; function prologu
movabs rax, offset .L.str ; *str = "Hello"
mov qword ptr [rbp - 24], rax
mov byte ptr [rbp - 1], 97 ; c = 'a'
lea rax, [rbp - 1] ; p = &c
mov qword ptr [rbp - 16], rax ; p = &c
mov edi, 1 ; malloc(1)
call malloc ; malloc(1)
xor ecx, ecx
mov qword ptr [rbp - 16], rax ; malloc(1)
mov rax, qword ptr [rbp - 16] ; p[0]='a'
mov byte ptr [rax], 97 ; p[0]='a'
mov eax, ecx
add rsp, 32
pop rbp
ret
.L.str:
.asciz "Hello"
在执行p[0]='a'时,两个代码sn-ps执行相同的指令,只是使用不同的寄存器来存储p指向的地址。但是,p[0]='a' 行之前的 malloc() 调用会产生很大的不同。 p 指向的内存位置现在由操作系统分配,分配给该内存地址的操作是有效的。
如果没有malloc() 调用,您只是试图写入未初始化的内存,您还没有从操作系统请求那块内存。尽管说明看起来相同,但您的操作系统看起来却不同。