简体   繁体   中英

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. The main:

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

    return 0;
}

And the 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:

  1. How come when I examined arr_p I saw that its address is the same as arr which is in the demo function?
  2. 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.

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. 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. 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). 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. Since you return a pointer to that variable, the pointer is said to be dangling . Dereferencing the pointer makes your program have undefined behavior .

One way to fix it is to malloc (memory allocate) the memory you need.

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 ? I expected that arr inside demo will be deallocated as we got out of demo scope.

The life of the arr object has ended and reading the memory addresses previously occupied by arr makes your program have undefined behavior . 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 }; 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.

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. 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. The C standard defines an abstract lifetime of the array arr in demo and says that lifetime ends when the function returns. 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. 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(). 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(). Either you can also allocate some contiguous block of memory using malloc() or you can declare your array in demo() as static array. 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.

Solved this by declaring a struct with a fixed size char array in it and returning that struct from the 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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