繁体   English   中英

从 C 中的 function 返回一个数组

[英]Returning an array from function in C

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

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

    return 0;
}

和 function:


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

当我取消引用arr_p时,我可以看到demo function 中设置的 0-9 整数。两个问题:

  1. 为什么当我检查arr_p时发现它的地址与demo function 中的arr相同?
  2. demo_p指向已在demo中的未释放数据(0-9 数字)? 我预计当我们离开demo scope 时, demo中的arr将被释放。

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

如果您在编写返回指向本地分配数组的指针的程序时没有收到错误消息,这并不意味着它没问题。 (虽然,这意味着您确实应该获得更新的编译器,因为任何体面的现代编译器都会对此发出警告。)

如果您编写的程序返回一个指向本地分配数组的指针并且它似乎可以工作,那也不意味着它没问题。 对此要非常小心:一般而言,在编程中,尤其是在 C 中,看似有效并不能证明您的程序没问题。 您真正想要的是让您的程序以正确的理由运行

假设你租了一套公寓。 假设,当您的租约到期并且您搬出时,您的房东没有从您那里取回您的钥匙,但也没有更换锁。 假设,几天后,你意识到你在一个壁橱的后面忘记了一些东西。 假设你不经询问就偷偷溜回去试图收集它。 接下来发生什么?

  • 碰巧的是,您的钥匙仍然可以在锁中使用。 这是完全出乎意料,还是有点出乎意料,还是保证有效?
  • 碰巧,你忘记的项仍然在衣柜里。 它尚未清除。 这是完全出乎意料,还是有点出乎意料,还是保证会发生?
  • 最后,无论是你的老房东,还是警察,都没有因为你的这种侵入行为而与你搭讪。 再说一次,这是完全出乎意料,还是有点出乎意料,还是几乎完全在意料之中?

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

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

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

修复它的一种方法是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 数字)? 我预计, arr内部demo ,我们的下了车将被释放demo范围。

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

……我注意到函数中内部数组的内存没有被释放……

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

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的抽象生命周期,并说生命周期在函数返回时结束。 它进一步说,当它指向的对象的生命周期结束时,指针的值变得不确定。

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

亲爱的,如您所知,在局部函数中声明的变量的存在仅在该局部作用域内。 完成所需的任务后,函数将终止,然后销毁局部变量。 当您尝试从 demo() 函数返回一个指针时,问题是指针指向的数组将在我们退出 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

当我一直试图从 function 返回 char 数组时遇到过类似的情况。但我总是需要一个固定大小的数组。

通过在其中声明一个具有固定大小的 char 数组的结构并从 function 返回该结构来解决此问题:

#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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM