Optimizing Storage for Constant Data

Remember that the code in your program is backed not by the swap file but directly by its EXE and DLL files. If several instances of your program are running, the same EXE and DLL files will be mapped to each process's virtual address space. What about constant data? You would want that data to be part of the program rather than have it copied to another block of address space that's backed by the swap file.

You've got to work a little bit to ensure that constant data gets stored with the program. First consider string constants, which often permeate your programs. You would think that these would be read-only data, but guess again. Because you're allowed to write code like this:

char* pch = "test";
*pch = `x';

"test" can't possibly be constant data, and it isn't.

If you want "test" to be a constant, you must declare it as an initialized const static or global variable. Here's the global definition:

const char g_pch[] = "test";

Now g_pch is stored with the code, but where, specifically? To answer that, you must understand the "data sections" that the Visual C++ linker generates. If you set the link options to generate a map file, you'll see a long list of the sections (memory blocks) in your program. Individual sections can be designated for code or data, and they can be read-only or read/write. The important sections and their characteristics are listed here.

.textCodeRead-onlyProgram code
.rdataDataRead-onlyConstant initialized data
.dataDataRead/writeNonconstant initialized data
.bssDataRead/writeNonconstant uninitialized data

The .rdata section is part of the EXE file, and that's where the linker puts the g_pch variable. The more stuff you put in the .rdata section, the better. The use of the const modifier does the trick.

You can put built-in types and even structures in the .rdata section, but you can't put C++ objects there if they have constructors. If you write a statement like the following one:

const CRect g_rect(0, 0, 100, 100);

the linker puts the object into the .bss section, and it will be backed separately to the swap file for each process. If you think about it, this makes sense because the compiler must invoke the constructor function after the program is loaded.

Now suppose you wanted to do the worst possible thing. You'd declare a CString global variable (or static class data member) like this:

const CString g_str("this is the worst thing I can do");

Now you've got the CString object (which is quite small) in the .bss section, and you've also got a character array in the .data section, neither of which can be backed by the EXE file. To make matters worse, when the program starts, the CString class must allocate heap memory for a copy of the characters. You would be much better off using a const character array instead of a CString object.