1

I am trying to create a function which creates an array and return a pointer of the array: Here's my code:

#include<iostream>

using namespace std;

int* example() {
    int arr[] = {1,2,3};
    int *a = arr;
    return a;
}

int main() {
    int *a = example();
    cout << *a     << endl;
    cout << *(a+1) << endl;
    cout << *(a+2) << endl;
    return 0;
}

And here's the output:

1
1878006336
3

I don't know why am I getting that garbage value at 2nd line instead of 2.

Here are some of my observations regarding this:

  1. I am getting desired output if I manually create the array and pointer in the main() function.
  2. If I print the second line before the first line, I get desired output as:
2
1
3

What is the reason behind this weird behaviour?

Chris
  • 26,361
  • 5
  • 21
  • 42
  • You're invoking *undefined behavior* by dereferencing an address from an object (`arr` in `example`) that has long-since expired once the call to the function returns. The address held in `a` post-function-call is worthless. – WhozCraig Sep 16 '22 at 05:48
  • Please don't tag questions as both C and C++ unless your question is explicitly about the differences of both languages. Often enough the C++ answer to something is rather different than the C answer. – DevSolar Sep 16 '22 at 05:50
  • Answers below, but you need to understand the concept of *lifetime*. All objects have a lifetime and using them after their life has ended is a bug. In your case the lifetime of `arr` ends when the `example` exits. The fact that you returned a pointer to the array does not make any difference. This kind of pointer is known as a *dangling pointer*, – john Sep 16 '22 at 05:51
  • [Dupe1](https://stackoverflow.com/questions/19042552/returning-a-pointer-of-a-local-variable-c), [Dupe2](https://stackoverflow.com/questions/17997228/what-is-a-dangling-pointer) and [Dupe3](https://stackoverflow.com/questions/70943680/return-an-array-without-getting-a-dangling-pointer-as-result-in-c) – Jason Sep 16 '22 at 06:10

3 Answers3

2

The array arr is declared local to the function. As a result its lifetime ends when the function exits. We call this automatic lifetime. Returning a pointer to this memory invokes undefined behavior. The code might work the way you expect, or it might not.

To work around this, you need a lifetime that is not automatic, and which can be controlled. There are a few paths to this.

Manual memory management

You might use dynamic memory management with new and delete (or in this case delete []). This is generally discouraged as it creates many opportunities for mistakes.

int *example() {
    int *a = new int[3] { 1, 2, 3 };

    return a;
}

You would need to remember to delete this array.

int main() {
    int *a = example();

    cout << a[0] << endl;
    cout << a[1] << endl;
    cout << a[2] << endl;

    delete[] a;

    return 0;
}

Smart pointers

You could use a smart pointer, which makes explicitly deallocating unnecessary, as the memory it points to is deallocated when the smart pointer goes out of scope.

unique_ptr<int[]> example() {
    auto a = unique_ptr<int[]>(new int[3] { 1, 2, 3 });

    return a;
}

int main() {
    auto a = example();

    cout << a[0] << endl;
    cout << a[1] << endl;
    cout << a[2] << endl;

    return 0;
}

STL containers

The most idiomatic approach is to use an STL container class.

The std::array container maps directly to the fixed size arrays seen so far. The std::vector container is more appropriate if the size is unknown at compile time, which it often will be, making this a very common container to see in idiomatic C++ code.

As with the smart pointer, the memory does not have automatic storage duration, so it can live beyond the scope in which it's declared, and the data is deallocated when the container goes out of scope.

array<int, 3> example() {
    array<int, 3> a = { 1, 2, 3 };

    return a;
}

int main() {
    auto a = example();

    cout << a[0] << endl;
    cout << a[1] << endl;
    cout << a[2] << endl;

    return 0;
}
Chris
  • 26,361
  • 5
  • 21
  • 42
  • ...or ``, which really should be the go-to container of choice. If you add that, I'd happily remove my answer for your more elaborate one. – DevSolar Sep 16 '22 at 06:06
  • Finished that edit before I loaded your comment. Great minds think alike (or at least small minds seldom differ). – Chris Sep 16 '22 at 06:07
-2

Your function is allocating array in the local scope/stack, it won't be available for the outside caller. Allocate the array using new operator for C++, and malloc() for C, and then that pointer will be valid for the caller function.

Anand Sowmithiran
  • 2,591
  • 2
  • 10
  • 22
  • 1
    Question is tagged C++, which makes `malloc` the wrong answer to basically *anything*. ;-) Even if you change that to `new`, allocating memory in a function and expect the caller to call `delete` is bad design (should be a smart pointer, or at the very least a class with `new` in the constructor and `delete` in the destructor). – DevSolar Sep 16 '22 at 05:48
-2

arr[] is a local variable, it is stored on the stack, its content might not be preserved after the example() function has returned.

Here are 2 examples that can get the behavior that you want:

Example 1: Store array in global region

#include<iostream>

using namespace std;

int arr[] = {1,2,3};

int* example(){
    int* a = arr;
    return a;
}

int main(){
    int* a = example();
    cout<<*a<<endl;
    cout<<*(a+1)<<endl;
    cout<<*(a+2)<<endl;
    return 0;
}

Example 2: Store array in heap region

#include<iostream>
#include<cstdlib.h>

using namespace std;

int* example(){
    int* arr = malloc(3 * sizeof(int));
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    int* a = arr;
    return a;
}

int main(){
    int* a = example();
    cout<<*a<<endl;
    cout<<*(a+1)<<endl;
    cout<<*(a+2)<<endl;
    free(a);
    return 0;
}
  • Best not to mix C dynamic memory management into C++ code. – Chris Sep 16 '22 at 05:52
  • 1
    Same as for Anand's answer: `malloc` in a C++ program is a bad idea. Allocating memory dynamically in a function and passing out an owning pointer is a bad idea. `` is plain wrong. – DevSolar Sep 16 '22 at 05:52
  • If that's the reason, then why is it giving correct values when I tried to print second line before the first? It's data should be deleted and it should give garbage values at all the three lines. – Harsh Bansal Sep 16 '22 at 05:54
  • @HarshBansal the data "might" be deleted and give garbage value, not "must" – Huỳnh Thái Dương Sep 16 '22 at 05:57
  • 2
    @HarshBansal The beauty of undefined behavior: It's *undefined*, which might include "apparently correct" behavior under certain circumstances. – DevSolar Sep 16 '22 at 06:00
  • Aside from the `malloc` and `free` nonsense, I would suggest A. not perpetuating the lack of whitespace from the question in your answer, and B. not using `*(a+n)` when `a[n]` is available. – Chris Sep 16 '22 at 06:04
  • @HarshBansal *"Undefined behavior means anything can happen including but not limited to the program giving your expected output. But never rely on the output of a program that has UB. The program may just crash."* – Jason Sep 16 '22 at 06:09