Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




Memory Management

visual c en


Memory Management

The Win32 operating systems greatly simplify your task of dealing with memory, although they may require you to handle some things a bit differently from 16-bit environments. In this chapter, you will look at

The Win32 memory model



Memory allocation

Virtual memory

Shared memory

The Win32 Memory Model

Windows NT and Windows 95 make up the Win32 family of Microsoft operating systems. Win32 is a catchy title for marketing, but it also happens to be truly descriptive of the operating systems—that is, they use 32 bits. Although this may not seem like an earth-shattering advance, it makes a world of difference when you compare Win32 to 16-bit operating systems such as DOS or Win3.1 (if you even want to call Win3.1 an operating system).

When I say that WinNT and Win95 are 32-bit operating systems, I mean that they use 32-bit variables internally. Specifically, they support 32-bit pointers to memory, which take advantage of the 32-bit registers supported on Intel platforms, starting with the 80386 family of processors and other hardware platforms supported by Windows NT.

Way back when, developers were glad to have the segmented memory scheme supported on Intel's 8088—this allowed access to a whole megabyte of memory (or, 640KB under DOS). Compared to the previous architectures that allowed access to only 64KB, this really was something. However, as applications have risen to the task of consuming all resources available to them (just try running Word 6 in 1MB or less), this architecture has created countless headaches for developers.

First, you had to deal with near and far pointers: one for dealing with data within 64KB segments, and one for code and data farther away. This was particularly troublesome when you consider that there were 65,536 different possible values for a far pointer to one memory location, due to the overlapping segment and data registers.

Furthermore, to overcome the 1MB limit imposed by this architecture, and the 640KB DOS limit, several different (and incompatible) schemes were developed, including the oh-so- difficult-to-keep-straight extended memory (EMS) and expanded memory (XMS). Each of these required developers t 636g68g o jump through different sets of hoops to use memory above the 1MB or 640KB boundaries.

Before I go into any more horror stories (like how I actually worked with paper-tape programs once or how I built my own supercomputer out of raw sand and duct tape), let's look at the solution.

Win32 Memory Address Spaces

Under the Win32 operating systems, each process has access to its own flat address space of 4GB (232=4,294,967,296). No more segments, no more selectors, no more near and far pointers, no more near and far function calls, no more memory models. (Personally, I thought I had reached nirvana in the 32-bit flat memory model. That is, of course, until the vast number of new APIs available prompted me to write this book.)

Notice that each process has its own separate 4GB logical address space. Win32 allows each process to access its own memory, separate from other processes, with its own 32-bit logical address space. The operating system will convert this to a physical memory address for you, as well as handle virtual memory, as you will see.

Separate address spaces keep your processes insulated from other processes that have gone bad. If one application goes bonkers and starts spewing data through its memory space, other processes are much safer than they were in DOS, where all processes shared the same physical memory space.

This presents some difficulties if you are used to passing pointers between tasks. A given logical address (pointer value) in one process will not reference the same location in physical memory as an identical pointer value in another process. Win32 provides mechanisms for using shared memory, however, as you will see in a moment.

Win32 Reserved Addresses

Although I said that each process can access 4GB, some of this is reserved by the operating system, preventing your process from accessing these areas directly. Both WinNT and Win95 reserve the upper half of the 4GB space for their own use, leaving the lower 2GB available to your application. This should be sufficient for your application for at least a few years.

Windows 95 also reserves the lower 4MB of the address space to support compatibility with 16-bit DOS and Windows applications. Microsoft refers to this area as the Compatibility Arena in its documentation. Windows NT on the other hand, does not reserve this area.

The upper 2GB space is further divided by Windows 95 into the shared arena (between 2GB and 3GB) used for memory-mapped files, shared memory, and some 16-bit items; and the reserved system arena (between 3GB and 4GB) used to hold the privileged portions of the operating system's code.

Virtual Memory

There aren't many machines with 4GB of physical memory lying around, so Win32 makes use of virtual memory. Although a process can use the whole 4GB address range available to it, most don't. Windows maps the logical addresses used by your application to physical storage addresses as needed.

Note that I said storage addresses, rather than memory addresses. Windows makes use of a swapfile to store additional virtual memory above and beyond the RAM in the machine. Memory blocks that are not currently in use may be written out to disk, and new blocks of memory may be read in from disk as needed. Swapping to disk is much slower than simply having a gazillion bytes of RAM, but it effectively enables a process to use as much virtual memory as you have free disk space. I think you will agree that it is also much nicer to slow down a bit rather than just say, 'Out of Memory—Buy more and try again.'

All this is handled for you by the operating system, so you don't really have to worry about it. However, Windows does allow you to work with virtual memory directly, as you will see later in this chapter.

What a Difference 16 Bits Makes

Before you move on to memory management in Win32, it is appropriate to look at a few of the things that are different in 32-bit Windows, in comparison to the 16-bit Windows world. As you can probably guess, most of these issues have something to do with the change to 32-bit registers and pointers.

4-Byte Integers

To the great relief of those who have to port UNIX apps to Windows, variables of type int are now a full 4 bytes long. Intel processors (386 and up), use 4-byte values internally (as do the other NT platforms), so Windows now supports this as well. On hardware that supports 32-bit registers, it is actually faster in many cases to use 32-bit values.

If your application does anything that is dependent on int values being only 16 bits, you will need to make a few changes. However, the Windows WORD and DWORD types are still 16 and 32 bits, respectively, and will port nicely from previous versions of Windows.

Of Lands _near and _far

In Win32, all memory pointers are created equal—there is no such animal as a near or short pointer. All pointers reference one logical memory address, without regard to current segment register values. There is also no longer a need to do special processing for data that crosses segment boundaries. As such, the _near, _far, and _huge type modifiers no longer have any meaning and need not be used. However, if you have these in your applications, or just can't break the habit, the compiler will ignore them.

This also applies to the Windows-defined pointer types. For instance, LPSTR is the same as PSTR in Win32, although both types are defined to allow compatibility with previous versions. If you may need to port your application back to Win3.1, you should try to use the appropriate pointer type (LPSTR or PSTR, or similar types), even though the distinction will not affect your Win32 builds. You will also notice that the interfaces defined for Windows functions will use the types (near or far) appropriate for corresponding Win16 functions (if any).

Memory Models

In Win32, there is only one memory model. You will no longer need to choose from (or supply libraries for) the plethora of different memory models supported by DOS. (Everybody say Hallelujah!)

In addition, you will not need to use different runtime library functions for different memory models, nor will you need to supply libraries for different memory models. If your code uses model-specific calls, such as _fmalloc or _nmalloc, these are #defined to the appropriate function (malloc()) for you in WindowsX.h.

Windows Messages

In Win32, the format of many Windows messages has changed to deal with 32-bit handles and the fact that wParam is now 32 bits. For example, the WM_COMMAND message in 16-bit Windows puts the id in wParam and the hwnd and cmd in lParam. The Win32 version puts the id and cmd in wParam, and the hwnd takes the entire lParam.

It is possible to deal with these differences with #ifdefs, although it is much neater to use the message crackers provided in Windowsx.h, which allows you to use the message cracker macro to call a single handler function for the message.

If you will never need to support 16-bit windows, the message crackers need not be used. In fact, because Visual C++ 4.2 and above do not support 16-bit development (not even Win32s), the version of Windowsx.h included with VC++ 5.0 does not bother to define the message cracker variants for Win16. The macros are, however, defined for Win32 to provide compatibility.

Address Calculations

It should be obvious that if you do your own address calculations based on the segmented memory scheme, or use your own routines to poke at the segment registers, your code will have to change to support the flat memory space.

You also should be aware that memory allocated is not guaranteed to start on a segment boundary. Thus, any routines that set only the LOWORD of a pointer will no longer work.

Physical Memory Access

The Win32 operating systems don't generally allow your application to work with physical memory directly. Accordingly, API functions such as AllocSelector() and FreeSelector() are no longer available. In most cases, working with physical memory directly is not necessary; however, if you are writing device drivers or other such beasts, you can use the Win32 Device Driver Kit (DDK) to get at physical memory.

Simple Memory Allocation

This section introduces some of the simple memory allocation functions that are used in everyday programs. The simple allocation operations that you will look at can be divided into two basic categories: stack frame allocation and heap allocation.

Allocation on the Stack Frame

Stack frame allocation is actually so simple it doesn't even necessarily involve a function call. Memory allocated on the stack frame includes variables that are declared local to a function. These are allocated memory on the application's stack, which grows and shrinks as functions are called and returned. To illustrate this, the following function declares a few local variables that are allocated on the stack frame:

void MyFunction()

// myNestedInt no longer in scope
} // end MyFunction()

Here, you allocate an int, an array of chars, a structure, and a class object. The compiler will automatically allocate space on the stack frame for these variables and will automatically reclaim the memory when the variables go out of scope. In most cases, this happens when the function returns; although, as you see with myNestedInt, variables that are declared in nested brackets go out of scope and are deallocated when the nested bracket block ends.

Variables allocated in this manner are often called automatic variables, because the compiler allocates and deallocates the memory automatically. In addition, the constructors for automatic class variables are called when the memory for the object is allocated, and the destructors are called before the memory is deallocated.

Allocating variables in this manner is very convenient, because you don't have to worry about deallocating the memory or calling the destructors for objects—the compiler takes care of all of this. However, space available on the stack may be limited. To avoid filling up the stack, as well as to provide memory that outlives the scope of a particular function, you use heap allocation.

Heap Allocation

Heap allocation is performed when you use functions, such as new or malloc(), that are traditionally associated with dynamic data structures. Memory allocated in this way is allocated on the application's heap, which is global to the process. Unlike frame allocation, the amount of memory that may be allocated on the heap is limited only by the amount of virtual memory available on the system.

Memory allocated on the heap is persistent through the life of the process until it is deallocated by your application. The compiler will not deallocate any memory allocated on the heap for you (that is, until the process ends, at which point all memory associated with it is deallocated).

The following example shows how you can use new and malloc() to allocate memory on the heap, as well as how to deallocate the memory with delete and free:

// Allocating memory on the heap
int* pMyInt = new int;
char* pMyArray = new char[20];
myStructType* pMyStruct = new myStructType;
CMyClass* pMyObject = new CMyClass(3);
BYTE* myBuffer = malloc(MY_BUFFER_SIZE);

// Do stuff with your new memory

// De-allocate memory on the heap
delete pMyInt;
delete pMyArray;
delete pMyStruct;
delete pMyObject;
free(myBuffer);

Note that new will call the constructor for any class objects allocated, whereas malloc() simply allocates a block of memory.

When memory is allocated on the heap with new or malloc(), the operating system will keep track of the amount of memory allocated, so you will not need to worry about passing the size to delete or free. The system will deallocate the entire memory block pointed at by the value passed to delete or free.

Debug new and delete

The runtime libraries provided with Visual C++ support versions of new and delete that are enhanced to better support debugging. In debug builds, the new and delete operators will put markers in the heap that are used to detect memory leaks. If you have allocated memory and have not deallocated it when your program exits from a debugging session, the output window will display warnings about the memory that remains deallocated, or is 'leaked.'

In addition, you can receive more detailed information about memory leaks, including the line number and source module name of the allocation that caused the leak, by inserting the following lines in your application modules:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Note that release builds do not implement support for memory leak detection. The release versions of new and delete are built for speed rather than debug analysis.

CAUTION

You should not mix the use of the new/delete operators and the malloc/realloc/free functions on the same block of memory. For example, using free to delete a memory block that was allocated with new can result in corrupted memory, particularly in debug builds.

Resizing Memory Blocks

For blocks of memory allocated with malloc(), you can resize the block of memory allocated in the event your application finds that it requires a larger chunk of memory. This is done with the realloc() function:

char* ptr = malloc(100); // Allocate original block of memory

// Do stuff

ptr = realloc(ptr, 50); // Resize memory block

// Do more stuff

free(ptr); // free memory

The call to realloc() will effectively add 50 bytes to the buffer originally allocated with malloc(), although the value of the pointer assigned to ptr may be different from the value originally returned by malloc(). Any data in the memory block before the realloc() call will remain in the memory block, even if it is moved.

Heap Functions

In addition to the default heap used by your application, you can allocate your own private heaps with HeapCreate(). This can be useful in situations where you want to preallocate all the memory your application will use before proceeding.

Once you have created a private heap with HeapCreate(), you can use the HeapAlloc(), HeapReAlloc(), and HeapFree() functions much like the malloc(), realloc(), and free() functions to work with memory blocks in your private heap. In addition, you can retrieve the allocated size of a memory block with the HeapSize() function.

To ensure that only one thread can access a given heap, you can use the HeapLock() function to prevent other threads from accessing the heap until HeapUnlock() is called.

To get a list of all the heap handles used by a process, you can use the GetProcessHeaps() call or the GetProcessHeap() call to get the default heap used for the process for its stack, global, and automatic variables, as well as memory allocated with new and malloc().

The HeapWalk() function will return a list of all memory blocks allocated on a heap. You can check to see that all the control structures in a heap are valid with HeapValidate().

The HeapCompact() function will attempt simple defragmentation of the heap, although this will not be able to defragment badly fragmented heaps. You can destroy a heap with the DestroyHeap() function.

Windows Allocation Functions

The Windows GlobalAlloc() and LocalAlloc() family of functions are also supported, although the standard new or malloc() functions should generally be used. In Win32, there is no distinction between global and local memory, so GlobalAlloc() and LocalAlloc() are essentially the same.

CAUTION

In Win32, GlobalAlloc() can no longer be used to allocate memory to be shared between processes. To do this, you must use memory-mapped files.

In general, you should use the Windows allocation functions only where you need to use a handle to memory rather than a pointer, as in clipboard functions—unless, of course, you need to provide compatibility with 16-bit Windows.

GlobalMemoryStatus()

The GlobalMemoryStatus() function may be used to retrieve information about available physical and virtual memory. This function takes a pointer to a MEMORYSTATUS structure. Before calling GlobalMemoryStatus(), you should set the dwLength field of the MEMORYSTATUS structure to the length of the buffer (almost always sizeof(MEMORYSTATUS)). The call to GlobalMemoryStatus() will then fill in the rest of the structure with information about the percent of memory in use and the total and available amounts of physical, page file, and virtual memory.

Virtual Memory

The Win32 API allows your application to directly manipulate pages of virtual memory. This can be particularly useful in cases where you wish to reserve a large block of contiguous addresses, even though most of that space is never actually committed to physical storage.

In Win32, pages of virtual memory can be in four different states. A committed page is assigned to physical storage, whether in memory or in the swap file. Committed pages may also be locked, which will force them to stay in physical memory until unlocked. Reserved pages involve a reserved block of addresses, but are not assigned to any storage; free pages are unused altogether.

The VirtualAlloc() function may be used to allocate a range of addresses, starting at a specified logical address. Additional parameters to VirtualAlloc() allow you to specify access protection flags and to specify whether the memory will only be reserved or will also be committed to physical storage.

You may also call VirtualAlloc() again to commit pages of virtual memory previously reserved.

In addition, you can specify the PAGE_GUARD flag in the access protection flags to specify a guard page. If an attempt is made to access a guard page, a STATUS_GUARD_PAGE exception is raised, and the PAGE_GUARD flag is removed from that page.

In addition, you may force memory blocks to remain in physical memory (RAM) with GlobalLock(). This should be used carefully, because it will prevent Windows from managing this memory until GlobalUnlock() is called. If one process locks large chunks of memory, other processes may end up thrashing about trying to make do with whatever RAM is left.

The VirtualProtect() function allows you to alter the access protection flags for a block of memory; VirtualQuery() will return information about memory pages. If you need to access pages in other processes, you can do so with the VirtualProtectEx() and VirtualQueryEx() functions.

Memory Exceptions

If your application attempts to access a page that is not yet committed, an exception will be generated. You can handle these exceptions with the mechanisms discussed in Chapter 7, 'General-Purpose Classes.' With the virtual memory functions shown previously, you can implement your own virtual memory processing scheme to deal with the when and how of dealing with page faults.

Shared Memory

With the advent of separate memory spaces for processes, processes cannot simply allocate a block of memory with GlobalAlloc() and pass it to another process to share memory. When another process looks at a pointer allocated by another process, it just points off into some random location. However, Win32 does support mechanisms to share memory-mapped files between processes. Here, you will look at a method to do this with memory-mapped files.

NOTE

Visual C++ also supports Dynamic Data Exchange (DDE) and provides the DDEML library to help manage your DDE conversations. However, DDE was really intended to meet the needs of 16-bit Windows applications. It won't be discussed any further in this text, although the online documentation does cover the DDEML library.

Memory-Mapped Files

The virtual memory system in Win32 provides the capability of mapping pages of memory to pages in the paging file, or swapfile. Your applications can extend this capability by mapping memory to any file you like, including the system paging file itself. File-mapping can be used to provide faster and simpler file access, as well as to provide for shared memory.

To map a file to memory, you first use the CreateFileMapping() function, which takes a handle to a file that has already been opened with CreateFile(). For most shared memory applications, you can set this handle to 0xFFFFFFFF, which is used to indicate the system paging file. By using this special handle, you do not need to use CreateFile(), although you also won't have a disk file copy of the memory when you are done.

The second parameter to CreateFileMapping() is a pointer to a SECURITY_ATTRIBUTES structure, which may specify whether the returned handle can be inherited by child processes. In addition, a security descriptor pointer is included in the SECURITY_ATTRIBUTES. This is used by WinNT only to support its security mechanisms.

The third parameter allows you to specify the access protection for the block of memory. This value may include PAGE_READONLY, PAGE_READWRITE, or PAGE_WRITECOPY, which provides copy on write access to the committed pages. This means that when a process maps this memory and writes to it, it will get its own copy of the modified data, rather than actually writing to the shared space. In addition, you can combine several other flags to specify additional section attributes. These will allow you to disallow caching of the memory or to reserve the memory without committing it, as you saw in the virtual memory section.

Additional parameters allow you to specify the maximum size of the memory block. If the size of the memory block is larger than the file specified in the first parameter, the file will be enlarged.

The final parameter specifies a name for the memory-mapping object. This name can be used by other processes in a call to CreateFileMapping() or OpenFileMapping() to access the same file-mapping.

Once a memory-mapping object has been created with CreateFileMapping(), you call MapViewOfFile() to map a view of the file into the address space of a process. This function takes a handle returned by CreateFileMapping() or OpenFileMapping() and allows you to specify an access mode and number of bytes to map, as well as an offset into the file-mapping object. In addition, the MapViewOfFileEx() allows you to specify a suggested address at which to begin the mapping.

To see all this in action, let's look at the following example, which creates two simple console apps. The server (see Listing 8.1) writes a string to shared memory and waits until something longer is written. The client (see Listing 8.2) then reads the shared memory, writes a new string to it, and exits. True, this is a bit silly, but it does show how memory-mapped files work without a lot of other monkey business.

To run the following sample code, open two command prompts, starting the server in one and then starting the client in the other.

Listing 8.1. Memory-mapped file server.

#include <iostream.h>
#include <windows.h>
#include <stdio.h>

void main()


lpData = (LPSTR)MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS,
0,0,0);
if(lpData == NULL)


sprintf(lpData, 'Server Data String');

while(strlen(lpData) < 20)
Sleep(1000);

cout << 'Received: ' << lpData << endl;

UnmapViewOfFile(lpData);


Listing 8.2. Memory-mapped file client

#include <iostream.h>
#include <windows.h>
#include <stdio.h>

void main()


lpData = (LPSTR)MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS,
0,0,0);
if(lpData == NULL)


cout << 'Server Data: ' << lpData << endl;

sprintf(lpData, 'My client data string that is longer than 20');

UnmapViewOfFile(lpData);

Note that you call UnmapViewOfFile() when you are finished with the mapped view. This releases the mapped memory and performs a lazy write of the data to the file that was mapped (unless it was the swapfile, as it is here). If you want to write data to the disk immediately, you can use FlushViewOfFile() to write the mapped memory to the file.

Summary

In this chapter, you learned how Visual C++ can be used to manage memory under Win32. You also saw how Win32 implements a separate flat memory space for each process.

You took a look at how simple allocation functions such as new and malloc() work under Win32, and also explored the debug functions of new. You learned how to work with private heaps, using HeapCreate() and its related functions.

You saw how Win32 works with virtual memory and how your applications can use calls such as VirtualAlloc() to allocate and work with virtual memory directly.

Finally, you learned how your applications can use memory-mapped files to speed up file access and to share memory with other processes.


Document Info


Accesari: 958
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2024 )