从 C 中的函数返回一个数组

Returning an array from function in C

我写了一个 return 数组的函数,虽然我知道我应该 return 一个动态分配的指针,但我仍然想知道当我 returning 一个在函数内部局部声明的数组(没有将其声明为静态),当我注意到我的函数中内部数组的内存没有被释放时,我感到很惊讶,我把我的数组返回到 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相同?
  2. 为什么 demo_p 指向已经在 demo 中的未释放的数据(0-9 数字)?我预计 demo 内的 arr 将在我们离开 demo 范围时被释放。

arrdemo 中的局部变量,当您从函数中 return 时,它将被销毁。由于您 return 指向该变量的指针,因此该指针被称为 悬空 。取消引用指针会使您的程序具有 未定义的行为

修复它的一种方法是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

How come demo_p is pointing to data which is not deallocated (the 0-9 numbers) already in demo? I expected that arr inside demo will be deallocated as we got out of demo scope.

arr对象的生命周期已经结束,读取arr之前占用的内存地址会使你的程序未定义行为。您可能会看到旧数据或程序可能会崩溃 - 或者做一些完全不同的事情。 任何事情 都可能发生。

编程时必须注意的一件事是要充分注意规则所说的内容,而不仅仅是看起来有效的内容。规则说你不应该 return 一个指向本地分配数组的指针,这是一个真实的规则。

如果您在编写 return 指向本地分配数组的指针的程序时没有收到错误,那并不意味着它没问题。 (虽然,这意味着你真的应该得到一个更新的编译器,因为任何像样的现代编译器都会警告这个。)

如果您编写的程序 return 是一个指向本地分配数组的指针并且它似乎可以工作,但这也不意味着它没问题。对此要非常小心:一般来说,在编程中,但尤其是在 C 中,似乎可以工作 而不是 证明你的程序没问题。您真正想要的是让您的程序以正确的理由工作

假设你租了一间公寓。假设,当您的租约到期并且您搬出时,您的房东没有向您收取您的钥匙,但也没有更换锁。假设,几天后,您发现自己忘记了某个壁橱后面的东西。假设你不问就偷偷溜回去试图收集它。接下来会发生什么?

  • 碰巧,你的钥匙还在锁里。这是一个完全的惊喜,还是有点出乎意料,还是保证有效?
  • 碰巧,您遗忘的物品仍然在衣橱里。它尚未被清除。这是一个完全的惊喜,还是有点出乎意料,还是一定会发生?
  • 最后,无论是你的老房东,还是警察,都没有因为你的这种非法侵入行为而找过你。再说一遍,这是完全出乎意料,还是有点出乎意料,还是几乎完全在意料之中?

您需要知道的是,在 C 中,重用您不再被允许使用的内存几乎完全类似于偷偷溜回您不再租用的公寓。它可能有效,也可能无效。你的东西可能还在,也可能不在。你可能会遇到麻烦,也可能不会。没有办法预测会发生什么,也没有任何(有效的)结论可以从任何发生或不发生的事情中得出。

返回您的程序:像 arr 这样的局部变量通常存储在调用堆栈中,这意味着即使在函数 return 之后它们仍然存在,并且可能不会被覆盖直到调用下一个函数并将堆栈上的该区域用于其自己的目的(甚至可能不会)。因此,如果您 return 一个指向本地分配内存的指针,并立即取消对该指针的引用(在调用任何其他函数之前),它至少有点可能“工作”。这又类似于公寓的情况:如果还没有其他人搬进来,那么您遗忘的物品很可能仍然在那里。但这显然不是你可以依赖的东西。

… I noticed that the memory of the internal array in my function wasn't deallocated…

除非查看记录内存预留的数据(在本例中为堆栈指针),否则您无法注意到或观察到内存的重新分配。当内存被保留或释放时,这只是一个关于哪些内存可用或不可用的簿记过程。释放内存并不一定会擦除内存或立即将其重新用于其他目的。看内存不一定能告诉你有没有用

int arr[10] = { 0 }; 出现在函数内部时,它定义了一个数组,该数组在函数开始执行时自动分配(或者如果定义在某个嵌套范围内,则在函数执行的特定时间)。这通常通过调整堆栈指针来完成。在普通系统中,程序有一个称为堆栈的内存区域,堆栈指针包含一个地址,该地址标记当前保留使用的堆栈部分的末尾。当一个函数开始执行时,堆栈指针会发生变化,以便为该函数的数据保留更多内存。当函数执行结束时,堆栈指针被更改以释放该内存。

如果你保留一个指向该内存的指针(你如何做到这一点是另一回事,下面讨论),你不会“注意到”或“观察到”函数后立即对该内存的任何更改returns.这就是为什么您看到 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,并表示生命周期在函数returns 时结束。它进一步说,当指针指向的对象的生命周期结束时,指针的值变得不确定。

如果您的编译器只是简单地生成代码,就像您使用带有 -O0 的 GCC 编译以关闭优化时所做的那样,它通常会将地址保留在 p 中,您将看到所描述的行为多于。但是,如果您打开优化并编译更复杂的程序,编译器会尝试优化它生成的代码。它不是机械地生成汇编代码,而是试图找到执行程序定义行为的“最佳”代码。如果您使用具有不确定值的指针或尝试访问生命周期已结束的对象,则您的程序没有定义的行为,因此编译器的优化可能会产生新程序员无法预料的结果。

亲爱的,如您所知,在局部函数中声明的变量仅存在于该局部范围内。一旦完成所需的任务,函数就会终止,局部变量随后会被销毁。当您尝试 return 来自 demo() 函数的指针时,问题是指针指向的数组一旦我们退出 demo() 就会被销毁。因此,您确实在尝试 return 一个指向已释放内存的悬空指针。但是我们的规则建议我们不惜一切代价避免悬挂指针。

因此您可以通过在使用 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