在C中从函数返回数组

ldioqlga  于 2022-12-29  发布在  其他
关注(0)|答案(5)|浏览(186)

我写了一个返回数组的函数,虽然我知道我应该返回一个动态分配的指针,但我仍然想知道当我返回一个在函数内部本地声明的数组时会发生什么(没有声明它为静态),当我注意到我的函数中的内部数组的内存没有被释放时,我感到很惊讶,我把我的数组返回给了main。

int main()
{
    int* arr_p;
    arr_p = demo(10);

    return 0;
}

和功能:

int* demo(int i)
{
    int arr[10] = { 0 };
    for (int i = 0; i < 10; i++)
    {
        arr[i] = i;
    }
    return arr;
}

当我解引用arr_p时,我可以看到demo函数中的0-9整数集。
1.为什么当我检查arr_p时,我看到它的地址与demo函数中的arr相同?
1.为什么demo_p指向demo中尚未释放的数据(0-9数字)?我预计demo中的arr将在我们超出demo范围时被释放。

myss37ts

myss37ts1#

编程时需要注意的一件事是,注意规则的内容,而不仅仅是看起来有效的内容,规则说不应该返回指向本地分配数组的指针,这是一条真实的的规则。
如果你在编写一个返回指向本地分配数组的指针的程序时没有得到错误,这并不意味着它是正确的(尽管,这意味着你真的应该得到一个更新的编译器,因为任何像样的现代编译器都会警告你这一点)。
如果你写了一个程序,返回一个指向本地分配数组的指针,并且它看起来工作正常,这也不意味着它是好的,在这一点上要非常小心:一般来说,在编程中,尤其是在C语言中,看起来工作并不能证明你的程序是好的,你真正想要的是你的程序工作的原因是正确的。
假设你租了一套公寓,假设租期到了,你搬出去了,房东没有从你那里拿钥匙,也没有换锁;假设几天后,你发现自己把东西忘在了一个壁橱的后面;假设你没有问一声,就偷偷溜回去拿,接下来会发生什么?

  • 碰巧的是,你的钥匙还能在锁里用。这是完全出乎意料的,还是稍微出乎意料的,还是保证能用?
  • 碰巧的是,你忘记的东西还在壁橱里,还没有被清理出来。这是一个完全的惊喜,还是有点出乎意料,还是肯定会发生?
  • 最后,无论是你的老房东还是警察,都没有因为你的非法侵入行为而找你麻烦。再问一次,这是完全出乎意料,还是有点出乎意料,还是完全意料之中?

你需要知道的是,在C语言中,重用你不再被允许使用的内存就像偷偷溜回你不再租的公寓一样。它可能会成功,也可能不会。你的东西可能还在那里,也可能不在那里。你可能会陷入麻烦,也可能不会。没有办法预测会发生什么。无论发生了什么或没有发生什么,你都不能得出(有效的)结论。
返回到您的程序:像arr这样的局部变量通常存储在调用堆栈中,这意味着即使在函数返回之后,它们仍然存在,并且可能不会被覆盖,直到下一个函数被调用并将堆栈中的该区域用于自己的目的(甚至可能在那时也不会)。因此,如果返回一个指向本地分配内存的指针,并立即解引用该指针(在调用任何其他函数之前),它至少有可能“工作”,这又一次类似于apartment的情况:如果还没有其他人搬进来,很可能你遗忘的东西还在那里,但显然它不是你可以依赖的东西。

4zcjmb1e

4zcjmb1e2#

arrdemo中的一个局部变量,当你从函数返回时,它将被销毁。因为你返回了一个指向该变量的指针,所以该指针被称为"悬空"。解引用该指针会使你的程序具有"未定义的行为"。
解决这个问题的一个方法是malloc(内存分配)您需要的内存。
示例:

#include <stdio.h>
#include <stdlib.h>

int* demo(int n) {
    int* arr = malloc(sizeof(*arr) * n);  // allocate

    for (int i = 0; i < n; i++) {
        arr[i] = i;
    }

    return arr;
}

int main() {
    int* arr_p;
    arr_p = demo(10);
    printf("%d\n", arr_p[9]);
    free(arr_p)                 // free the allocated memory
}

输出:

9

为什么demo_p指向demo中尚未释放的数据(0 - 9数字)?我预计demo中的arr将在我们超出demo范围时被释放。
arr对象的生命周期 * 已经 * 结束,读取arr之前占用的内存地址会使程序出现 * 未定义的行为 。您可能会看到旧数据,或者程序可能崩溃-或者执行完全不同的操作。 任何事情 * 都可能发生。

z9gpfhce

z9gpfhce3#

...我注意到我的函数中的内部数组的内存没有被释放...
内存的重新分配不是您可以注意到或观察到的事情,除非查看记录内存保留的数据(在本例中为堆栈指针)。当内存被保留或释放时,那只是一个关于哪些内存可用或不可用的簿记过程。释放内存并不一定会擦除内存或立即将其重新用于其他用途。查看内存并不一定能告诉您它是否在使用中。
int arr[10] = { 0 };出现在函数中时,它定义了一个数组,该数组在函数开始执行时自动分配(或在函数执行的某些时候,如果定义是在某个嵌套作用域中)。这通常是通过调整堆栈指针来完成的。在通用系统中,程序有一个内存区域叫做堆栈,堆栈指针包含一个地址,该地址标记当前保留使用的堆栈部分的结束。当函数开始执行时,堆栈指针被改变以保留更多的内存用于该函数的数据。当函数执行结束时,改变堆栈指针以释放该存储器。
如果你保留一个指向该内存的指针(如何做是另一回事,下面将讨论),你将不会在函数返回后立即“注意到”或“观察到”该内存的任何变化,这就是为什么你看到arr_p的值是arr的地址,这也是为什么你看到该内存中的旧数据。
如果你调用了其他函数,堆栈指针会根据新函数进行调整,这个函数通常会将内存用于它自己的目的,然后内存的内容会发生变化,你在arr中的数据会消失,初学者遇到的一个常见的例子是:

int main(void)
{
    int *p = demo(10);
    // p points to where arr started, and arr’s data is still there.

    printf("arr[3] = %d.\n", p[3]);
    // To execute this call, the program loads data from p[3]. Since it has
    // not changed, 3 is loaded. This is passed to printf.

    // Then printf prints “arr[3] = 3.\n”. In doing this, it uses memory
    // on the stack. This changes the data in the memory that p points to.

    printf("arr[3] = %d.\n", p[3]);
    // When we try the same call again, the program loads data from p[3],
    // but it has been changed, so something different is printed. Two
    // different things are printed by the same printf statement even
    // though there is no visible code changing p[3].
}

回到如何拥有内存指针的副本,编译器遵循C标准中抽象指定的规则。C标准在demo中定义了数组arr的抽象 lifetime,并指出该lifetime在函数返回时结束。它进一步指出,指针的值在它所指向的对象的生命周期结束时变得不确定。
如果你的编译器只是简单地生成代码,就像你使用GCC和-O0来关闭优化一样,它通常会把地址保留在p中,你会看到上面描述的行为。但是,如果你打开优化并编译更复杂的程序,编译器会试图优化它生成的代码。它试图找到执行你的程序的定义行为的“最佳”代码。2如果你使用一个具有不确定值的指针或者试图访问一个生命周期已经结束的对象,你的程序没有定义行为,所以编译器的优化可能产生新手程序员意想不到的结果。

vql8enpb

vql8enpb4#

正如你所知道的,在local函数中声明的变量只存在于这个局部作用域中。一旦所需的任务完成,函数就会终止,局部变量随后也会被销毁。因为你正试图从demo返回一个指针()函数,但问题是指针指向的数组在我们结束演示后将被销毁()。所以你确实试图返回一个指向释放内存的悬空指针。但是我们的规则建议我们不惜任何代价避免悬空指针。
所以你可以在使用free()释放内存后重新初始化它来避免这种情况。你也可以使用malloc()分配一些连续的内存块,或者你可以在demo()中声明你的数组为静态数组。这样当本地函数成功退出时,也会保存分配的内存常量。
感谢您发送编修。

#include<stdio.h>
#define N 10

int demo();
int main()
{
   int* arr_p;
   arr_p = demo();
   printf("%d\n", *(arr_p+3));
}

int* demo()
{
   static int arr[N];

for(i=0;i<N;i++)
{
   arr[i] = i;
}

   return arr;
}

OUTPUT : 3

或者你也可以写......

#include <stdio.h>
#include <stdlib.h>
#define N 10

int* demo() {
   int* arr = (int*)malloc(sizeof(arr) * N);

   for(int i = 0; i < N; i++)
{
   arr[i]=i;
}

   return arr;
}

int main()
{
  int* arr_p;
  arr_p = demo();
  printf("%d\n", *(arr_p+3));
  free(arr_p);  
  
  return 0;
}

OUTPUT : 3
pbgvytdp

pbgvytdp5#

当我试图从函数中返回字符数组时,也有类似的情况。但我总是需要一个固定大小的数组。
通过声明一个包含固定大小字符数组的结构体并从函数返回该结构体解决了此问题:

#include <time.h>

typedef struct TimeStamp
{
    char Char[9];
} TimeStamp;

TimeStamp GetTimeStamp()
{
    time_t CurrentCalendarTime;

    time(&CurrentCalendarTime);

    struct tm* LocalTime = localtime(&CurrentCalendarTime);

    TimeStamp Time = { 0 };

    strftime(Time.Char, 9, "%H:%M:%S", LocalTime);

    return Time;
}

相关问题