Search code examples
c++arrayslinuxc++17stack-overflow

Passing Large Stack Allocated Array as Function Argument Causes Stack Overflow


I allocated a large array on the stack after adjusting the stack size using setrlimit. When this array is declared in main() and then passed as argument to a method, I get a segmentation fault. When the array is declared as a local variable inside a method, the code runs without any seg fault. I am running the code on an Amdx86-64 linux box with 8GB RAM.

#include <iostream>
#include <sys/resource.h>

using usll = unsigned long long;
void sumArray0();
double sumArray1(double c[], usll dim); 

int main(int argc, char* argv[])
{
    const rlim_t stackSize = 3 * 1024UL * 1024UL * 1024UL;
    struct rlimit rl;
    int result;

    printf("The required value of stackSize is %lu\n", stackSize);  
    result = getrlimit(RLIMIT_STACK, &rl);

    if (result == 0)
    {
        if (rl.rlim_cur < stackSize)
        {
            rl.rlim_cur = stackSize;
            result = setrlimit(RLIMIT_STACK, &rl);

            if (result != 0)
            {
                fprintf(stderr, "setrlimit returned result = %d\n", result);
            }
            else
            {
                printf("The new value of stackSize is %lu\n", rl.rlim_cur);
            }
        }
    }

    // // This seg faults
    // const usll DIM = 20000UL * 18750UL;  
    // double c[DIM];

    // for (usll i{}; i<DIM; ++i)
    // {
    //     c[i] = 5.0e-6;
    // }
    // double total = sumArray1(c, DIM); // Seg fault occurs here

    sumArray0(); // This works

    std::cout << "Press enter to continue";
    std::cin.get();

    return 0;
}

void sumArray0()
{
    double total{};
    const usll DIM = 20000UL * 18750UL; 
    double c[DIM];

    for (usll i{}; i<DIM; ++i)
    {
        c[i] = 5.0e-6;
    }

    for (usll i{}; i<DIM; ++i)
    {
        total += c[i];
    }

    std::cout << "Sum of the elements of the vector is " << total << std::endl;
}

double sumArray1(double c[], usll dim)
{
    double total{};

    for (usll i{}; i<dim; ++i)
    {
        total += c[i];
    }

    return total;
}

My questions are:
Why am I getting a stackoverflow in the first case?
Is it because a new chunk of memory is requested in the call to the method sumArray1()?
Isn't the array accessed via a pointer when passed as argument to the method?

As recommended here, Giant arrays causing stack overflows, I always use std::vector and never allocate large arrays on the stack to prevent issues like above. I will highly appreciate it if anyone knows of any tweaks, tricks or workarounds that can make the call to sumArray1() work.


Solution

  • So it seems to me that you are compiling without optimizations enabled, because otherwise the array in main would be optimized-out completely, because the calculations done with it are never used in any observable manner. (This is bad for anything, really. Even for debugging purposes you should probably use -Og.)

    In the comments you mention that you are using the -fPIC flag for compilation. This prevents gcc from inlining the sumArray1 call, so that the array cannot be optimized-out, no matter the optimization flags. You should probably use -fpie (which may already be the default) for an executable instead of -fPIC, which is meant for shared libraries and comes with these performance penalties, see also here and here.

    If that is the case, then to answer your question: The problem is not the passing to a function. The problem is that stack space is allocated when the function is entered, so before the limits are set.

    Now "allocating" here just means modifying the stack pointer, but it is very much possible that anywhere in this stack frame is accessed by main before the limit is set. In particular the compiler can reorder the location of the variables in the stack frame however it wants or it may add a stack guard depending on your compiler settings, etc.

    Any such access before the limit is set would cause a segmentation fault.

    Note that the compiler is also free to inline functions. So, even doing things in sumArray0 may cause you trouble if the compiler decides to inline that function call, because then the array will become part of main's stack frame with the same issues as discussed above applying.

    The compiler may recognize that inlining a function with large stack frame is potentially dangerous and not do so, but that is something that you would need to check against in the compiler documentation.

    In any case, compilers and operating systems do not expect programs to use large stack frames. That is not their purpose. The heap/free store is specifically there to handle memory allocations that are larger than usual stack frames. It is usually good practice to enable warnings for large stack frames and heed them.