Optimizing Data Segment Usage
There are a lot of things that you can do to improve your Win32 application's runtime memory use under CE. However, you can also reap some fairly impressive gains in memory efficiency simply by being aware of where the linker puts data in your program's executable.
An executable file basically has two kinds of things in it: executable code and data. On all of the Windows hosts, code is "pure," meaning that you can't modify it at runtime. For this reason, the part of a Windows executable file that contains the code is designated "read only," so it is of little concern to us here. The rest of the executable file is devoted to data, of which there are two kinds: read-only data, such as literal strings and constants; and read/write data, which is usually either data with global scope or data defined by using the static keyword.
Because access permissions are set on a page-by-page basis once the program is in memory, these data storage areas in your program have to be created in page-sized increments. At a minimum, your executable file contains one page for static ("read-only") data, and one page for read/write data. This means that there well may be some unused space in the data storage areas of your executable file, and you could profit by using it instead of making allocation requests.
On the other side of the coin, you may discover that one or the other of your data sections is just slightly larger than one page, which causes the allocation of a lot of unnecessary space. Moving things around a bit may shrink your data sections and get you in under that critical threshold.
So, how do you find out how your application file lays out the data sections?
Set the linker output option for generating a mapfile; this contains data section names, lengths, and base addresses.
Here's how to interpret the parts of the link map that show how your program's data is organized.
Figure 1: An Abbreviated Link Map
ExeFileMem Timestamp is 3bbcb82d (Thu Oct 04 13:27:41 2001) Preferred load address is 00010000 Start Length Name Class 0001:00000000 00000c48H .text CODE 0002:00000000 00000054H .rdata DATA 0002:00000054 00000028H .idata$2 DATA 0002:0000007c 00000014H .idata$3 DATA 0002:00000090 00000088H .idata$4 DATA 0002:00000118 0000023cH .idata$6 DATA 0002:00000354 00000000H .edata DATA 0003:00000000 00000088H .idata$5 DATA 0003:00000088 00000004H .CRT$XCA DATA 0003:0000008c 00000004H .CRT$XCZ DATA 0003:00000090 00000004H .CRT$XIA DATA 0003:00000094 00000004H .CRT$XIZ DATA 0003:00000098 00000004H .CRT$XPA DATA 0003:0000009c 00000004H .CRT$XPZ DATA 0003:000000a0 00000004H .CRT$XTA DATA 0003:000000a4 00000004H .CRT$XTZ DATA 0003:000000a8 00000010H .data DATA 0003:000000b8 00000810H .bss DATA 0004:00000000 00000104H .pdata DATA 0005:00000000 000001f0H .rsrc$01 DATA 0005:000001f0 000005d4H .rsrc$02 DATA
First, let's look at the two places read-only data is stored, the .rdata section and the resource data sections, .rsrc$01 and rsrc$02.
Here are a few lines from the ExeFileMem example. Basically, it is a typical "Hello World" generated application, but with some data declarations added for the purposes of producing the mapfile above.
// Global Variables: HINSTANCE hInst; // The current instance HWND hwndCB; // The command bar handle //make a large global allocation int iLargeIntArray; //the const keyword means this //datum is read-only const int iConstDataItem = 1;
Notice the last declaration, iConstDataItem. This variable is declared with the keyword, which makes it non modifiable.
Porting Tip: Non-modifiable data is placed in the program section .rdata.
We've seen (a lot) that memory allocation operations typically have a granularity of one page. To designate an item or group of items as "read only," they have to reside together in their own page(s), because that is the smallest increment on which we can set access permissions. Any datum that we declare with the const keyword has to end up in a read-only data section because that's the only way to ensure that it is protected from modification.
Now, examine the map file line that gives the length of the .rdata section:
Start Length Name Class 0002:00000000 00000054H .rdata DATA
The length of this section is 54h (84 decimal); however, when loaded, this section will consume an entire page of program memory. Yikes! We need to find some other non-modifiable data we can move to this page, or this space will be wasted.
The first possibility that springs to mind is string data, and most applications have a number of them, stored as resources. Resources have their own section in the executable file layout. Here are the map file lines that apply to them.
Start Length Name Class 0005:00000000 000001f0H .rsrc$01 DATA 0005:000001f0 000005d4H .rsrc$02 DATA
There's a good reason not to move string data out of the resource files and into the static data area. CE provides us with a special version of the LoadString() function used to load string resources that allows you to read the string in place. Calling LoadString() like this returns a pointer to the string:
LPCTSTR pStringResource; //if the last parameter is NULL, get back a pointer to a //constant string pStringResource = (LPCTSTR)LoadString( hInstance, IDS_STRING, NULL );
You can use the string, but you can't write back to it. Always use this function to load string resources you don't intend to modify.
There are lots of other possibilities for moving data to the .rdata section, but leave string resources in the resource section so you can use the CE version of LoadString().