为了更好地理解 C 中的间接级别,了解编译器如何组织其内存可能会很有启发性。
考虑以下示例:
void function1 (int var1, int var2) { ... }
在这种情况下,function1 将接收 2 个变量。但是如何?
这些变量将被放入调用堆栈内存中。这是一种线性的、LIFO(后进先出)类型的分配策略。
在调用 function1() 之前,编译器会将var1
thenvar2
放入调用栈,并增加调用栈 ceil 的位置。然后它会调用function1()。function1() 知道它必须获取 2 个参数,因此它会在调用堆栈中找到它们。
function1() 完成后会发生什么?好吧,调用堆栈递减,所有进入其中的变量都被简单地“忽略”,这几乎与“被擦除”相同。
所以很明显,无论你在 function1() 期间对这些变量做什么,调用程序都会丢失。如果必须保持调用程序可用的任何内容,则需要将其提供到可以在调用堆栈递减步骤中幸存下来的内存空间中。
请注意,对于 function1() 中的任何变量,逻辑都是相同的:在 function1() 完成后,调用函数将无法使用它。本质上,仍然存储在 function1() 内存空间中的任何结果都是“丢失”的。
有两种方法可以从函数中检索可用结果。
主要是将函数的结果保存到调用程序/函数的变量中。考虑这个例子:
int* foo3(size_t n) { return (int*) malloc(n); }
void callerFunction()
{
int* p;
p = foo3(100); // p is still available after foo3 exits
}
第二个更复杂的方法是提供一个指向存在于调用内存空间中的结构的指针作为参数。
考虑这个例子:
typedef struct { int* p; } myStruct;
void foo4(myStruct* s) { s->p = (int*) malloc(100); }
void callerFunction()
{
myStruct s;
foo4(&s); //p is now available, inside s
}
它读起来更复杂,但也更强大。在此示例中,myStruct 包含一个指针,但结构可能要复杂得多。这打开了提供无数变量作为函数结果的视角,而不是像前面的 foo3() 示例那样仅限于基本类型。
那么当你知道你的结构实际上是一个简单的基本类型时会发生什么呢?好吧,你可以只提供一个指向它的指针。顺便说一下,指针本身就是一种基本类型。因此,如果您想获得修改后的指针的结果,您可以提供一个指向指针的指针作为参数。在那里我们找到了 foo1()。
void foo1(int **p) {
*p = (int *) malloc(100); // caller can get the memory
}