让我们将这个答案分成两个观点,因为标准只会使对该主题的理解复杂化,但无论如何它们都是标准:)。
两部分的共同主题
void func1() {
char *s = "hello";
char *c;
int b;
c = (char*)malloc(15);
strcpy(c, s);
}
第一部分:从标准的角度来看
根据标准,有一个有用的概念称为自动变量持续时间,其中变量的空间在进入给定范围时自动保留(具有统一的值,又名:垃圾!),它可以在这样的范围内设置/访问或不访问,并且释放这样的空间以供将来使用。 注意:在 C++ 中,这也涉及到对象的构造和销毁。
因此,在您的示例中,您有三个自动变量:
-
char *s,它被初始化为 "hello" 的地址。
-
char *c,它保存垃圾直到它被稍后的赋值初始化。
-
int b,它在其整个生命周期中都保存着垃圾。
顺便说一句,标准未指定存储如何与函数一起使用。
第二部分:从现实世界的角度来看
在任何体面的计算机架构上,您都会发现一种称为堆栈的数据结构。堆栈的目的是保存可以被自动变量使用和回收的空间,以及一些空间用于递归/函数调用所需的一些东西,并且如果编译器可以用作保存临时值(用于优化目的)的地方决定。
堆栈以PUSH/POP 方式工作,即堆栈向下增长。让我更好地解释一下。想象一个像这样的空栈:
[Top of the Stack]
[Bottom of the Stack]
如果你,例如,PUSH 和 int 的值 5,你会得到:
[Top of the Stack]
5
[Bottom of the Stack]
那么,如果你PUSH-2:
[Top of the Stack]
5
-2
[Bottom of the Stack]
而且,如果您POP,您检索-2,堆栈看起来就像之前-2 是PUSHed。
栈底是一个屏障,可以向上移动到PUSHing 和POPing。在大多数架构上,堆栈的底部由称为 堆栈指针 的processor register 记录。将其视为unsigned char*。你可以减少它,增加它,对它进行指针运算等等。一切都是为了对堆栈的内容进行黑魔法。
在堆栈中为自动变量保留(空间)是通过减少它来完成的(记住,它是向下增长的),而释放它们是通过增加它来完成的。基于此,之前的理论PUSH -2 是伪汇编中类似这样的简写:
SUB %SP, $4 # Subtract sizeof(int) from the stack pointer
MOV $-2, (%SP) # Copy the value `-2` to the address pointed by the stack pointer
POP whereToPop 只是反过来
MOV (%SP), whereToPop # Get the value
ADD %SP, $4 # Free the space
现在,编译 func1() 可能会产生以下伪汇编(注意:您不会完全理解这一点):
.rodata # Read-only data goes here!
.STR0 = "hello" # The string literal goes here
.text # Code goes here!
func1:
SUB %SP, $12 # sizeof(char*) + sizeof(char*) + sizeof(int)
LEA .STR0, (%SP) # Copy the address (LEA, load effective address) of `.STR0` (the string literal) into the first 4-byte space in the stack (a.k.a `char *s`)
PUSH $15 # Pass argument to `malloc()` (note: arguments are pushed last to first)
CALL malloc
ADD %SP, 4 # The caller cleans up the stack/pops arguments
MOV %RV, 4(%SP) # Move the return value of `malloc()` (%RV) to the second 4-byte variable allocated (`4(%SP)`, a.k.a `char *c`)
PUSH (%SP) # Second argument to `strcpy()`
PUSH 4(%SP) # First argument to `strcpy()`
CALL strcpy
RET # Return with no value
我希望这对你有所启发!