简体   繁体   English

从 C 中的 function 返回一个数组

[英]Returning an array from function in C

I've written a function that returns an array whilst I know that I should return a dynamically allocated pointer instead, but still I wanted to know what happens when I am returning an array declared locally inside a function (without declaring it as static), and I got surprised when I noticed that the memory of the internal array in my function wasn't deallocated, and I got my array back to main.我写了一个 function 返回一个数组,虽然我知道我应该返回一个动态分配的指针,但我仍然想知道当我返回一个在 function 中本地声明的数组时会发生什么(没有将其声明为静态),当我注意到我的 function 中内部数组的 memory 没有被释放时,我感到很惊讶,我把我的数组恢复到 main。 The main:主要的:

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

    return 0;
}

And the function:和 function:


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

When I dereference arr_p I can see the 0-9 integers set in the demo function. Two questions:当我取消引用arr_p时,我可以看到demo function 中设置的 0-9 整数。两个问题:

  1. How come when I examined arr_p I saw that its address is the same as arr which is in the demo function?为什么当我检查arr_p时发现它的地址与demo function 中的arr相同?
  2. How come demo_p is pointing to data which is not deallocated (the 0-9 numbers) already in demo ? demo_p指向已在demo中的未释放数据(0-9 数字)? I expected that arr inside demo will be deallocated as we got out of demo scope.我预计当我们离开demo scope 时, demo中的arr将被释放。

One of the things you have to be careful of when programming is to pay good attention to what the rules say, and not just to what seems to work.编程时必须注意的一件事是要注意规则所说的内容,而不仅仅是看起来有效的内容。 The rules say you're not supposed to return a pointer to a locally-allocated array, and that's a real, true rule.规则说你不应该返回一个指向本地分配数组的指针,这是一个真实的规则。

If you don't get an error when you write a program that returns a pointer to a locally-allocated array, that doesn't mean it was okay.如果您在编写返回指向本地分配数组的指针的程序时没有收到错误消息,这并不意味着它没问题。 (Although, it means you really ought to get a newer compiler, because any decent, modern compiler will warn about this.) (虽然,这意味着您确实应该获得更新的编译器,因为任何体面的现代编译器都会对此发出警告。)

If you write a program that returns a pointer to a locally-allocated array and it seems to work, that doesn't mean it was okay, either.如果您编写的程序返回一个指向本地分配数组的指针并且它似乎可以工作,那也不意味着它没问题。 Be really careful about this: In general, in programming, but especially in C, seeming to work is not proof that your program is okay.对此要非常小心:一般而言,在编程中,尤其是在 C 中,看似有效并不能证明您的程序没问题。 What you really want is for your program to work for the right reasons .您真正想要的是让您的程序以正确的理由运行

Suppose you rent an apartment.假设你租了一套公寓。 Suppose, when your lease is up, and you move out, your landlord does not collect your key from you, but does not change the lock, either.假设,当您的租约到期并且您搬出时,您的房东没有从您那里取回您的钥匙,但也没有更换锁。 Suppose, a few days later, you realize you forgot something in the back of one closet.假设,几天后,你意识到你在一个壁橱的后面忘记了一些东西。 Suppose, without asking, you sneak back to try to collect it.假设你不经询问就偷偷溜回去试图收集它。 What happens next?接下来发生什么?

  • As it happens, your key still works in the lock.碰巧的是,您的钥匙仍然可以在锁中使用。 Is this a total surprise, or mildly unexpected, or guaranteed to work?这是完全出乎意料,还是有点出乎意料,还是保证有效?
  • As it happens, your forgotten item still is in the closet.碰巧,你忘记的项仍然在衣柜里。 It has not yet been cleared out.它尚未清除。 Is this a total surprise, or mildly unexpected, or guaranteed to happen?这是完全出乎意料,还是有点出乎意料,还是保证会发生?
  • In the end, neither your old landlord, nor the police, accost you for this act of trespass.最后,无论是你的老房东,还是警察,都没有因为你的这种侵入行为而与你搭讪。 Once more, is this a total surprise, or mildly unexpected, or just about completely expected?再说一次,这是完全出乎意料,还是有点出乎意料,还是几乎完全在意料之中?

What you need to know is that, in C, reusing memory you're no longer allowed to use is just about exactly analogous to sneaking back in to an apartment you're no longer renting.您需要知道的是,在 C 中,重用您不再允许使用的内存几乎完全类似于潜入您不再租用的公寓。 It might work, or it might not.它可能有效,也可能无效。 Your stuff might still be there, or it might not.你的东西可能还在那里,也可能不在。 You might get in trouble, or you might not.你可能会遇到麻烦,也可能不会。 There's no way to predict what will happen, and there's no (valid) conclusion you can draw from whatever does or doesn't happen.没有办法预测会发生什么,也没有(有效的)结论可以从发生或不发生的任何事情中得出。

Returning to your program: local variables like arr are usually stored on the call stack, meaning they're still there even after the function returns, and probably won't be overwritten until the next function gets called and uses that zone on the stack for its own purposes (and maybe not even then).回到你的程序:像arr这样的局部变量通常存储在调用堆栈中,这意味着即使在函数返回之后它们仍然存在,并且可能不会被覆盖,直到下一个函数被调用并使用堆栈上的该区域它自己的目的(甚至可能不是)。 So if you return a pointer to locally-allocated memory, and dereference that pointer right away (before calling any other function), it's at least somewhat likely to "work".因此,如果您返回一个指向本地分配内存的指针,并立即取消引用该指针(在调用任何其他函数之前),它至少有可能“工作”。 This is, again, analogous to the apartment situation: if no one else has moved in yet, it's likely that your forgotten item will still be there.这再次类似于公寓的情况:如果还没有其他人搬进来,您遗忘的物品很可能还在那里。 But it's obviously not something you can ever depend on.但这显然不是您可以依赖的东西。

arr is a local variable in demo that will get destroyed when you return from the function. arrdemo中的一个局部变量,当你从函数返回时它会被销毁。 Since you return a pointer to that variable, the pointer is said to be dangling .由于您返回一个指向该变量的指针,因此该指针被称为dangling Dereferencing the pointer makes your program have undefined behavior .取消引用指针会使您的程序具有未定义的行为

One way to fix it is to malloc (memory allocate) the memory you need.修复它的一种方法是malloc (内存分配)您需要的内存。

Example:例子:

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

Output:输出:

9

How come demo_p is pointing to data which is not deallocated (the 0-9 numbers) already in demo ?为什么demo_p指向demo已经没有释放的数据(0-9 数字)? I expected that arr inside demo will be deallocated as we got out of demo scope.我预计, arr内部demo ,我们的下了车将被释放demo范围。

The life of the arr object has ended and reading the memory addresses previously occupied by arr makes your program have undefined behavior . arr对象的生命周期已经结束,读取arr先前占用的内存地址会使您的程序出现未定义的行为 You may be able to see the old data or the program may crash - or do something completely different.您可能会看到旧数据,或者程序可能会崩溃——或者做一些完全不同的事情。 Anything can happen.任何事情都可能发生。

… I noticed that the memory of the internal array in my function wasn't deallocated… ……我注意到函数中内部数组的内存没有被释放……

Deallocation of memory is not something you can notice or observe, except by looking at the data that records memory reservations (in this case, the stack pointer).内存的释放不是您可以注意到或观察到的,除非通过查看记录内存预留的数据(在本例中为堆栈指针)。 When memory is reserved or released, that is just a bookkeeping process about what memory is available or not available.当内存被保留或释放时,这只是一个关于哪些内存可用或不可用的簿记过程。 Releasing memory does not necessarily erase memory or immediately reuse it for another purpose.释放内存不一定会擦除内存或立即将其重新用于其他目的。 Looking at the memory does not necessarily tell you whether it is in use or not.查看内存不一定会告诉您它是否在使用中。

When int arr[10] = { 0 };int arr[10] = { 0 }; appears inside a function, it defines an array that is allocated automatically when the function starts executing (or at certain times within the function execution if the definition is in some nested scope).出现在函数内部,它定义了一个数组,该数组在函数开始执行时自动分配(如果定义在某个嵌套范围内,则在函数执行中的某些时间)。 This is commonly done by adjusting the stack pointer.这通常通过调整堆栈指针来完成。 In common systems, programs have a region of memory called the stack, and a stack pointer contains an address that marks the end of the portion of the stack that is currently reserved for use.在普通系统中,程序有一个称为堆栈的内存区域,堆栈指针包含一个地址,该地址标记当前保留供使用的堆栈部分的末尾。 When a function starts executing, the stack pointer is changed to reserve more memory for that function's data.当函数开始执行时,堆栈指针会发生变化,以便为该函数的数据保留更多内存。 When execution of the function ends, the stack pointer is changed to release that memory.当函数的执行结束时,堆栈指针被更改以释放该内存。

If you keep a pointer to that memory (how you can do that is another matter, discussed below), you will not “notice” or “observe” any change to that memory immediately after the function returns.如果您保留指向该内存的指针(如何做到这一点是另一回事,将在下面讨论),您将不会在函数返回后立即“注意到”或“观察”该内存的任何更改。 That is why you see the value of arr_p is the address that arr had, and it is why you see the old data in that memory.这就是为什么您看到arr_p的值是arr拥有的地址,这就是为什么您会看到该内存中的旧数据。

If you call some other function, the stack pointer will be adjusted for the new function, that function will generally use the memory for its own purposes, and then the contents of that memory will have changed.如果调用其他函数,堆栈指针将针对新函数进行调整,该函数通常会出于自己的目的使用内存,然后该内存的内容将发生变化。 The data you had in arr will be gone.您在arr拥有的数据将消失。 A common example of this that beginners happen across is:初学者遇到的一个常见例子是:

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].
}

Going back to how you can have a copy of a pointer to memory, compilers follow rules that are specified abstractly in the C standard.回到如何获得指向内存的指针的副本,编译器遵循 C 标准中抽象指定的规则。 The C standard defines an abstract lifetime of the array arr in demo and says that lifetime ends when the function returns. C 标准在demo定义了数组arr的抽象生命周期,并说生命周期在函数返回时结束。 It further says the value of a pointer becomes indeterminate when the lifetime of the object it points to ends.它进一步说,当它指向的对象的生命周期结束时,指针的值变得不确定。

If your compiler is simplistically generating code, as it does when you compile using GCC with -O0 to turn off optimization, it typically keeps the address in p and you will see the behaviors described above.如果您的编译器简单地生成代码,就像您使用带有-O0 GCC 编译以关闭优化时所做的那样,它通常将地址保留在p ,您将看到上述行为。 But, if you turn optimization on and compile more complicated programs, the compiler seeks to optimize the code it generates.但是,如果您打开优化并编译更复杂的程序,编译器会尝试优化它生成的代码。 Instead of mechanically generating assembly code, it tries to find the “best” code that performs the defined behavior of your program.它不是机械地生成汇编代码,而是尝试找到执行程序定义行为的“最佳”代码。 If you use a pointer with indeterminate value or try to access an object whose lifetime has ended, there is no defined behavior of your program, so optimization by the compiler can produce results that are unexpected by new programmers.如果您使用具有不确定值的指针或尝试访问生命周期已结束的对象,则程序没有定义的行为,因此编译器的优化可能会产生新程序员无法预料的结果。

As you know dear, the existence of a variable declared in the local function is within that local scope only.亲爱的,如您所知,在局部函数中声明的变量的存在仅在该局部作用域内。 Once the required task is done the function terminates and the local variable is destroyed afterwards.完成所需的任务后,函数将终止,然后销毁局部变量。 As you are trying to return a pointer from demo() function ,but the thing is the array to which the pointer points to will get destroyed once we come out of demo().当您尝试从 demo() 函数返回一个指针时,问题是指针指向的数组将在我们退出 demo() 后被销毁。 So indeed you are trying to return a dangling pointer which is pointing to de-allocated memory.因此,您确实正在尝试返回一个指向取消分配内存的悬空指针。 But our rule suggests us to avoid dangling pointer at any cost.但是我们的规则建议我们不惜一切代价避免悬空指针。

So you can avoid it by re-initializing it after freeing memory using free().因此,您可以通过在使用 free() 释放内存后重新初始化来避免它。 Either you can also allocate some contiguous block of memory using malloc() or you can declare your array in demo() as static array.您也可以使用 malloc() 分配一些连续的内存块,或者您可以将 demo() 中的数组声明为静态数组。 This will store the allocated memory constant also when the local function exits successfully.当本地函数成功退出时,这也将存储分配的内存常量。

Thank You Dear..谢谢你亲爱的..

#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

Or you can also write as......或者你也可以写成......

#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

Had the similar situation when i have been trying to return char array from the function. But i always needed an array of a fixed size.当我一直试图从 function 返回 char 数组时遇到过类似的情况。但我总是需要一个固定大小的数组。

Solved this by declaring a struct with a fixed size char array in it and returning that struct from the function:通过在其中声明一个具有固定大小的 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