- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 802字
- 2021-06-10 18:27:56
Types of Memory
In general, you can regard memory as being one of four types:
- Static or global
- String pool
- Automatic or stack
- Free store
When you declare a variable at the global level, or if you have a variable declared in a function as static, then the compiler will ensure that the variable is allocated from memory that has the same lifetime as the application--the variable is created when the application starts and deleted when the application ends.
When you use a string literal, the data will also, effectively, be a global variable, but stored in a different part of the executable. For a Windows executable, string literals are stored in the .rdata PE/COFF section of the executable. The .rdata section of the file is for read-only initialized data, and hence you cannot change the data. Visual C++ allows you to go a step further and gives you an option of string pooling. Consider this:
char *p1 { "hello" };
char *p2 { "hello" };
cout << hex;
cout << reinterpret_cast<int>(p1) << endl;
cout << reinterpret_cast<int>(p2) << endl;
In this code, two pointers are initialized with the address of the string literal hello. In the following two lines, the address of each pointer is printed on the console. Since the << operator for char* treats the variable as a pointer to a string, it will print the string rather than the address of the pointer. To get around this, we call the reinterpret_cast operator to convert the pointer to an integer and print the value of the integer.
If you compile the code at the command line using the Visual C++ compiler, you will see two different addresses printed. These two addresses are in the .rdata section and are both read-only. If you compile this code with the /GF switch to enable string pooling (which is default for Visual C++ projects), the compiler will see that the two string literals are the same and will only store one copy in the .rdata section, so the result of this code will be that a single address will be printed on the console twice.
In this code, the two variables p1 and p2 are automatic variables, that is, they are created on the stack created for the current function. When a function is called, a chunk of memory is allocated for the function and this contains space for the parameters passed to the function and the return address of the code that called the function, as well as space for the automatic variables declared in the function. When the function finishes, the stack frame is destroyed.
The calling convention of the function determines whether the calling function or the called function has the responsibility to do this. In Visual C++, the default is the __cdecl calling convention, which means the calling function cleans up the stack. The __stdcall calling convention is used by Windows operating system functions and the stack clean up is carried out by the called function. More details will be given in the next chapter.
Automatic variables only last as long as the function and the address of such variables only make any sense within the function. Later in this chapter, you will see how to create arrays of data. Arrays allocated as automatic variables are allocated on the stack to a fixed size determined at compile time. It is possible with large arrays that you could exceed the size of the stack, particularly with functions that are called recursively. On Windows, the default stack size is 1 MB, and on x86 Linux, it is 2 MB. Visual C++ allows you to specify a bigger stack with the /F compiler switch (or the /STACK linker switch). The gcc compiler allows you to change the default stack size with the --stack switch.
The final type of memory is dynamic memory created on the free store or sometimes known as the heap. This is the most flexible way of using memory. As the name suggests, you allocate memory at runtime of a size determined at runtime. The implementation of the free store depends on the C++ implementation but you should regard the free store as having the same lifetime as your application, so memory allocated from the free store should last at least as long as your application.
However, there are potential dangers here, particularly for long-lived applications. All memory allocated from the free store should be returned back to the free store when you have finished with it so that the free store manager can reuse the memory. If you do not return memory appropriately, then potentially the free store manager could run out of memory, which will prompt it to ask the operating system for more memory, and consequently, the memory usage of your application will grow over time, causing performance issues due to memory paging.