为什么我可以从一个函数返回一个由malloc设置的数组:
int *dog = (int*)malloc(n * sizeof(int));
而不是由
设置的数组 int cat[3] = {0,0,0};
"cat[]"数组返回一个警告。
谢谢你的帮助
这是一个范围的问题。
int cat[3]; // declares a local variable cat
局部变量与malloc内存
局部变量存在于堆栈中。当这个函数返回时,这些局部变量将被销毁。此时,用于存储数组的地址被回收,因此您无法保证它们的内容。
如果您调用malloc,您将从堆中分配内存,因此内存将在函数的生命周期之后持续存在。
如果函数应该返回一个指针(在本例中是指向整型数组的第一个地址的指针),该指针应该指向良好的内存。Malloc是确保这一点的方法。
避免Malloc您不必在函数内部调用malloc(尽管这样做是正常和适当的)。
或者,您可以将一个地址传递给应该保存这些值的函数。你的函数将完成计算值的工作,并将填满给定地址的内存,然后返回。
实际上,这是一个常见的模式。但是,如果这样做,您将发现不需要返回地址,因为您已经知道正在调用的函数的外部地址。正因为如此,更常见的是返回一个指示例程成功或失败的值,比如int,而不是返回相关数据的地址。
这样,函数的调用者就可以知道是否成功填充了数据,或者是否发生了错误。
#include <stdio.h> // include stdio for the printf function
int rainCats (int *cats); // pass a pointer-to-int to function rainCats
int main (int argc, char *argv[]) {
int cats[3]; // cats is the address to the first element
int success; // declare an int to store the success value
success = rainCats(cats); // pass the address to the function
if (success == 0) {
int i;
for (i=0; i<3; i++) {
printf("cat[%d] is %d r", i, cats[i]);
getchar();
}
}
return 0;
}
int rainCats (int *cats) {
int i;
for (i=0; i<3; i++) { // put a number in each element of the cats array
cats[i] = i;
}
return 0; // return a zero to signify success
}
注意,这里不需要调用malloc,因为cats[3]是在main函数内部声明的。main中的局部变量只会在程序退出时销毁。除非程序非常简单,否则malloc将用于创建和控制数据结构的生命周期。
还要注意,rainCats被硬编码为返回0。在rainCats内部不会发生任何会导致它失败的事情,比如试图访问文件、网络请求或其他内存分配。更复杂的程序有许多失败的原因,因此通常有一个很好的理由返回成功代码。
在运行的程序中有两个关键的内存部分:堆栈和堆。堆栈也称为调用堆栈。
在调用函数时,有关参数、返回位置和函数作用域中定义的所有变量的信息被压入堆栈。(过去的情况是,C变量只能在函数的开头定义。主要是因为它使编译器编写者的工作更轻松。)
当你从函数返回时,堆栈上的所有东西都弹出并消失(很快当你进行更多的函数调用时,你会覆盖内存,所以你不想指向它!)
任何时候分配内存都是从堆分配的。这是内存的另一部分,由分配管理器维护。一旦你"保留"了一部分,你就要对它负责,如果你不想再指着它,你应该让经理知道。如果您丢弃了指针,并且不能再要求释放它,这就是泄漏。
你也应该只看你说你想要的那部分内存。不仅覆盖你说你想要的部分,而且覆盖过去(或之前)的内存部分是一种经典的漏洞利用技术:将信息写入存储计算机指令而不是数据的内存部分。关于编译器和运行时如何管理的知识可以帮助专家弄清楚如何做到这一点。设计良好的操作系统可以防止它们这样做。
堆:int *dog = (int*)malloc(n*sizeof(int*));
堆栈:int cat[3] = {0,0,0};
因为int cat[3] = {0,0,0};
声明了一个只在函数被调用时才存在的自动变量
对于初始化的自动char数组,C中有一个特殊的"分配",因此可以返回带引号的字符串,但它不能泛化到其他数组类型。
cat[]是在你调用的函数的堆栈上分配的,当该堆栈被释放时,内存被释放(当函数返回堆栈时,应视为释放)。
如果你想要做的是在调用帧中填充一个int型数组,传递一个指针到你在调用帧中控制的对象;
void somefunction() {
int cats[3];
findMyCats(cats);
}
void findMyCats(int *cats) {
cats[0] = 0;
cats[1] = 0;
cats[2] = 0;
}
当然,这是人为的,我已经硬编码数组长度为3,但这是你必须做的,从一个调用函数获取数据。
单个值可以工作,因为它被复制回调用帧;
int findACat() {
int cat = 3;
return cat;
}
findACat
中的从findAtCat复制到调用帧,因为它是一个已知的量,编译器可以为您完成。指针指向的数据不能复制,因为编译器不知道要复制多少。
当你定义一个像'cat'这样的变量时,编译器会给它分配一个地址。名称和地址之间的关联仅在定义的范围内有效。在auto变量的情况下,从定义开始作用域就是函数体。
自动变量在堆栈上分配。堆栈上的同一个地址在不同的时间与不同的变量相关联。当你返回一个数组时,实际返回的是数组第一个元素的地址。不幸的是,在返回之后,编译器可以并且将为了完全不相关的目的重用该存储。您在源代码级别看到的将是您返回的变量在没有明显原因的情况下神秘地改变。
现在,如果您确实必须返回一个初始化的数组,您可以将该数组声明为静态。静态变量具有永久而不是临时的存储分配。您需要记住,对该函数的连续调用将使用相同的内存,因此在进行下一次调用之前,可能需要将前一次调用的结果复制到其他地方。
另一种方法是将数组作为参数传入,并在函数中写入它。这样,调用函数就拥有了变量,栈变量的问题就不会出现了。
这些都没有多大意义,除非你仔细研究堆栈是如何工作的。祝你好运。
不能返回数组。返回一个指针。这不是一回事。
你可以返回一个指向malloc()
分配的内存的指针,因为malloc()
已经分配了内存并保留给你的程序使用,直到你显式地使用free()
来释放它。
不能返回指向局部数组分配的内存的指针,因为一旦函数结束,局部数组就不再存在。
这是对象生命周期的问题,而不是作用域、堆栈或堆的问题。虽然这些术语与对象的生命周期有关,但它们并不等同于生命周期,重要的是你要返回的对象的生命周期。例如,动态分配的对象具有从分配到释放的生命周期。局部变量的生命周期可能在变量的作用域结束时结束,但如果它是静态的,它的生命周期不会在此结束。
使用malloc()
分配的对象的生命周期是直到使用free()
函数释放该对象为止。因此,当您使用malloc()
创建对象时,只要您没有释放该对象,就可以合法地返回指向该对象的指针——当函数结束时,它仍然是活动的。事实上,你应该小心地对指针做一些事情,这样它就会被记住,否则就会导致泄漏。
自动变量的生存期在变量的作用域结束时结束(因此作用域与生存期相关)。因此,从函数返回指向这样一个对象的指针是没有意义的——一旦函数返回,该指针就会失效。
现在,如果你的局部变量是static
而不是automatic,那么它的生存期就会超出它所在的作用域(因此作用域不等于生存期)。因此,如果函数有一个局部静态变量,那么即使函数已经返回,该对象仍然是活动的,并且从函数返回一个指向静态数组的指针是合法的。虽然这带来了一套全新的问题,因为只有一个对象的实例,所以从函数返回它多次可能会导致共享数据的问题(它基本上只工作,如果数据在初始化后没有改变,或者有明确的规则,什么时候可以和不可以改变)。
从这里的另一个答案中取出的另一个例子是关于字符串字面值的——指向它们的指针可以从函数中返回,不是因为作用域规则,而是因为一个规则,该规则规定字符串字面值的生存期一直延续到程序结束。