2

For my application I need to declare a big std::array in global memory. Its total size is about 1GB big. So I declared a global variable just like this:

#include<array>

std::array<char,1000000000> BigGlobal; 

int main()
{
    //Do stuff with BigGlobal
}

The code compiles fine. When I run the application I am getting the error message:

The application was unable to start correctly (0xc0000018). Click OK to close the application

I am using Visual Studio 2017. I am aware of the fact, that there is a MSVC Linker Option for the stack reserve size. But it is only relevant for local variables not for global variables. Can you please help me to fix the issue?

BlueTune
  • 1,023
  • 6
  • 18
  • 3
    Is there a reason why you can't use a `std::vector` at global scope, and resize it in the first statement of `main`? – Bathsheba Apr 21 '20 at 07:43
  • Are you compiling this as a 32bit or 64bit executable? – Botje Apr 21 '20 at 07:50
  • @Botje I am compiling in 64bit mode. – BlueTune Apr 21 '20 at 07:55
  • @Bathsheba Your proposed workaround seems to work. Would you like to post your proposal as an answer? – BlueTune Apr 21 '20 at 07:58
  • @BlueTune: I've given it a go. – Bathsheba Apr 21 '20 at 08:03
  • "For my application I need to declare a big std::array in global memory". No you don't. There almost always is a better way than a global variable, especially when we are talking about a giant array. – Eric Apr 21 '20 at 08:06
  • @jason-liam - you've marked this question as already answered, but those questions address stack variables and not large **global** variables. While those questions you linked to are related, they don't address the actual question that the OP asked about large global variables. – Vijay Varadan Dec 23 '22 at 19:39

3 Answers3

3

C++ compilers are full of limits - some make it into the standard, some don't.

Common limits include a size limit on the length of variable names, the number of times a function can call itself (directly or indirectly), the maximum size of memory grabbed by a variable with automatic storage duration and so on.

You've hit upon another limit with your use of std::array.

A sensible workaround in your case could be to use a std::vector as the type for the global, then resize that vector in the first statement of main. Of course this assumes there is no use of the global variable prior to program control reaching main - if there is then put it somewhere more explicit.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
0

tl;dr

The solution is to increase the stack size. e.g., when using Visual Studio 2022, pass in /STACK:<reserve>[,commit]; details here: https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170&viewFallbackFrom=vs-2017.

Clarification: this does not mean that the global object is created on the stack; it's likely due to stack space needed for temporary objects created to initialize the object contents; see the details below for the case when a std::array (whose size is known at compile time) is used instead of a std::vector. The std::array is wholly allocated in the data segment, IIRC. The internal storage for the vector is heap allocated, but the vector instance itself is in the data segment; the additional stack space is needed in the case of the vector for the temporaries.

Example with Details

The issue is that even globals have to be initialized by some thread. In the case of MSVC, from what I can tell, it's done by the main thread. If we have a global vector of 564,168 64-bit prime numbers declared something like this:

std::vector<int64_t> knownInt24PrimesVector = {
         2,          3,          5,          7,         11, 
        13,         17,         19,         23,         29, 
        31,         37,         41,         43,         47, 
...
...
   8388461,    8388473,    8388539,    8388547,    8388571, 
   8388581,    8388587,    8388593, 
};

The program compiles fine, but when it is run, it will crash. It shows in the debugger as:

Unhandled exception at 0x00007FF620824107 in isprime.exe: 0xC00000FD: Stack overflow (parameters: 0x0000000000000001, 0x0000002F19603000).

The call stack looks like this:

    isprime.exe!__chkstk() Line 109
>   isprime.exe!`dynamic initializer for 'knownInt24PrimesVector''() Line 112842
    [External Code] 

The threads window in the debugger shows this:

Not Flagged >   16252   0   Main Thread Main Thread isprime.exe!__chkstk
Not Flagged     22980   0   Worker Thread   ntdll.dll thread    ntdll.dll!00007ffefdb30b14
Not Flagged     13112   0   Worker Thread   ntdll.dll thread    ntdll.dll!00007ffefdb30b14
Not Flagged     5580    0   Worker Thread   ntdll.dll thread    ntdll.dll!00007ffefdb30b14

As can be seen from the call stack and the threads window, the huge (~4.5MB) vector is getting initialized by Main Thread.

The default stack size is 1MB for programs compiled with MSVC. dumpbin shows this (the 100000 is in hexadecimal even though it doesn't have the 0x prefix):

> dumpbin.exe /headers .\isprime.exe | findstr /ispn stack
46:          100000 size of stack reserve
47:            1000 size of stack commit

Since the data is ~4.5MB in size, increasing the stack size via /STACK:0x00480000 fixes the problem. dumpbin of the rebuilt program shows the stack reserve as 0x480000, which is 4,718,592.

> dumpbin.exe /headers .\isprime.exe | findstr /ispn stack
46:          480000 size of stack reserve
47:            1000 size of stack commit

Note that with the Visual Studio 2022 using the v143 toolchain, using a std::array<int64_t, 564168> did not require the stack size to be changed from the default value, which is expected since the array size is known at compile time.

Similarly, neither did constructing the vector passing in the array::cbegin() and array::cend() iterators require a stack size increase.

Also, more of an FYI: Using the v141 toolchain (which VS 2017 ships with), the compiler crashed (no additional info other than a message to contact tech support) trying to compile the file.

Vijay Varadan
  • 629
  • 5
  • 18
-1

According to Does std::array<> guarantee allocation on the stack only?

std::array is allocated on the stack, not the heap so it is a bad idea to use it if you need a big chunk of memory

I would use a std::vector and do dynamic allocation.

This can be done as follows:

#include<vector>

static std::vector<char> BigGlobal; 

int main()
{
   // one time init: can be done anywhere.
   if (BigGlobal.empty())
   {
       BigGlobal.resize(1000000000);
   }

    //Do stuff with BigGlobal
}
Jean-Marc Volle
  • 3,113
  • 1
  • 16
  • 20
  • Global variables are neither stored in the stack or heap. See [SO](https://stackoverflow.com/questions/44359953/are-global-variables-in-c-stored-on-the-stack-heap-or-neither-of-them) – BlueTune Apr 21 '20 at 08:09
  • Do bear in mind that `main` cannot be called from itself (either directly or indirectly), so I think your `if` statement is unnecessary. Unless you're thinking in terms of guarding against a fork? – Bathsheba Apr 21 '20 at 08:10
  • i dont agree that it is a "bad idea". If it fits, I would prefer `std::array` over `std::vector` when possible – 463035818_is_not_an_ai Apr 21 '20 at 08:11
  • @Bathsheba You are right, with few knowledge about how the global variable will be used I showed how to ensure it is allocated only once but it's useless in the context of the example. – Jean-Marc Volle Apr 21 '20 at 08:19
  • @BlueTune Thanks for the clarification I forgot about BSS. I corrected my answer. – Jean-Marc Volle Apr 21 '20 at 08:22
  • @ idclev Can you clarify why you prefer std::array? The only reason I see is for embedded systems with sparse memory where you want to know at compilation time if your code memory needs are not bigger than available. – Jean-Marc Volle Apr 21 '20 at 08:25
  • @Jean-MarcVolle because imho using dynamic allocations as the default is a "bad idea" ;). Also for semantics, if I want a fixed size array that is `std::array` not `std::vector` – 463035818_is_not_an_ai Apr 21 '20 at 08:51