“Doctor, my stack is overflowing! What does it mean?! Is there a cure?!” The Doctor frequently sees questions like these, and he realizes it’s time for a general article on the subject of stack allocation, as well as the other memory allocation types, static and dynamic.
“Everybody’s got to be somewhere,” the saying goes. And so it is with your program’s data – it has to live somewhere in memory while it is being referenced (registers are a special kind of memory we won’t get into here.) The compiler, linker and operating system work together to determine exactly where in memory a piece of data is to reside. The simplest method of assigning locations is “static allocation”, where the data is assigned a fixed (static) address by the compiler and linker in the executable image (EXE). For example, if variable X is statically allocated at address 4000, it is always at address 4000 when that EXE is run, no matter what else is going on in the system. (DLLs can also have static data – it is allocated at a fixed offset from the base address where the DLL gets loaded.)
Static allocation is simple from the compiler’s perspective because all that is needed is to create a list of variables that need allocation, and lay them down in memory one after the other. A run-time advantage of static allocation is that it is usually easy and fast to access a fixed address and statically allocated data can be used from anywhere in the program. But static allocation has disadvantages too. First, if you have any reentrant or parallel code, the multiple code streams are both trying to use the same data, which may not be wanted. Second, if you have many routines which need a lot of memory just while they’re executing, the available address space can fill up quickly (for example, ten routines each of which declares a 1000×1000 REAL(8) array need a total of 80,000,000 bytes just for those arrays.) And perhaps most important, with static allocation you must know at compile-time how much memory you will want.
Up through Fortran 77, the Fortran standard was carefully written in a way so that static allocation was the only method needed. Even today, static allocation is the most widely used method – in Visual Fortran, COMMON blocks and most variables with the SAVE attribute are allocated statically. (Note that Compaq Visual Fortran, by default, implies SAVE for local routine variables unless it can see that the variable is always written before it is read.) [Intel Visual Fortran does not imply SAVE for local variables – you must specify that with a SAVE statement or use the /Qsave option if you want that.]
Dynamic allocation is the complete opposite of static allocation. With dynamic allocation, the running application must call a system routine to request a particular amount of memory (for example, 1000 bytes). The system routine looks to see if that request size is available in the collection (“heap”) of memory segments it has available. If the request can be satisfied, a range of memory addresses is marked as used and the starting address is returned to the program. If the heap is empty, the operating system expands the virtual address space of the process to replenish the heap, stopping only if there is no more virtual memory available. The program stores the base address in a pointer variable and then can access the memory. When the program no longer needs the memory, another system routine is called to “free” it – return it to the heap so that it can be used again by a future allocate call. You can think of this as similar to borrowing money from a bank, and then later paying it back (except that there’s no interest!)
The big advantage of dynamic allocation is that the program can decide at run-time how much memory to get, making it possible to create programs that can accommodate problems of any size. You are limited only by the total amount of virtual memory available to your process (a little less than 2GB in 32-bit Windows) and, as long as you keep your pointers separate, your allocation is separate from others in the application. However, if your program “forgets” to free the allocated memory, and no longer has the pointer through which it is referenced, the allocated memory becomes unusable until the program exits – a “memory leak”. Also, the allocate/free process can be slow, and accessing data through pointers can itself reduce run-time performance somewhat.
In Fortran, the ALLOCATE statement performs dynamic allocation, with DEALLOCATE being the “free” operation. In Visual
Fortran, one can use dynamic allocation in other ways, such as the C-style malloc/free routines, or by calling Win32 API routines to allocate memory.
Stack allocation appears to be the least understood of the three models. The “stack” is a contiguous section of memory assigned by the linker. The “stack pointer” is a register (ESP in the X86 architecture) which holds the current position in the stack. When a program starts executing, the stack pointer points to the top of the stack (just above the highest-addressed location in the stack. As routines are called, the stack pointer is decremented (subtracted from) to point to a section of the stack that the routine can use for temporary storage. (The previous value of the stack pointer is saved.) The routine can call other routines, which in turn create stack space for themselves by decrementing the stack pointer. When a routine returns to its caller, it cleans up by simply restoring the saved stack pointer value.
The stack is an extremely efficient way of creating “scratch space” for a routine, and the stack plays a prominent role in the mechanism of calling and passing arguments to routines. Visual Fortran uses the stack to create space for automatic arrays (local arrays whose size is based on a routine argument) and for temporary copies of arrays used in array expressions or when a contiguous copy of an array section must be passed to another routine. The problem is, however, that the total amount of stack space is fixed by the linker [on Windows], and if a routine tries to allocate more space than the stack can hold, the dreaded “stack overflow” error occurs.
On some other operating systems, OpenVMS for example, the OS can extend the stack as needed, limited only by the total
amount of virtual address space available. On Windows, however, the stack allocation is determined by the linker and defaults to a paltry 1MB in the Microsoft linker. You can change the allocation – for details, see the on-disk documentation topic “Stack, linker option setting size of” – but this works only for executable images (EXEs.) If you are building a DLL, it
doesn’t matter what you set the stack size to – it is the size specified by the EXE that calls your DLL, (for example, VB.EXE), which is used.
So, what can you do if changing the stack size is not an option? Reduce your code’s use of the stack. Replace automatic
arrays with allocatable arrays and ALLOCATE them to the desired size at the start of the routine (they will be automatically
deallocated on routine exit unless marked SAVE.) If passing a noncontiguous array section to another routine, have the called routine accept it as a deferred-shape array (an explicit interface is required). Future versions of Visual Fortran may allocate large temporary values dynamically rather than using the stack, but for now, being aware of the limits of
stack allocation is important.
[Edit January 11, 2008] An update to Intel Visual Fortran 9.1 added the /heap-arrays option which tells the compiler to use the heap (dynamic allocation) for arrays that it would otherwise put on the stack. This can be handy if you can’t avoid the stack arrays otherwise. It does add a slight performance penalty for the allocate and deallocate, but applications processing large arrays probably would not notice. See the documentation for more details.]
(From Intel Developer Zone, copied with permission)