Dale E. Rogerson
Microsoft Developer Network Technology Group
Created: August 6, 1992
Revised: January 21, 1993
"The business side is easy—easy! ...if you're any good at math at all, you understand business. It's not its own deep, deep subject. It's not like C++."
William Gates, Upside Magazine, April 1992
There are two sample applications associated with this technical article.
Click to open or copy the files for the OWNER sample application.
Click to open or copy the files for the NEWOPR sample application.
Many developers ask the question, "Do I need to overload the new operator for Windows™–based applications?" when they start programming in C++ with the Microsoft® C/C++ version 7.0 compiler. These developers want to conserve selectors while allocating memory from the global heap. Fortunately, the C/C++ version 7.0 run-time library allows developers to reduce selector consumption without overloading the new operator.
This article examines the behavior of the new operator in a Windows-based program. It provides an overview of new, discusses whether you should overload new, examines the C++ ambient memory model, and discusses large-model C++ programming and dynamic-link library (DLL) ownership issues.
Two sample applications, newopr and owner, illustrate the concepts in this technical article. A bibliography of suggested reading material is included at the end of the article.
This section provides an overview of the new operator, the _fmalloc function, and the _nmalloc function.
The new operator calls malloc directly. In small or medium model, it calls the near version of malloc, which is _nmalloc. In large model, it calls _fmalloc.
Alarms are probably ringing in the heads of experienced programmers. In the past, Microsoft has recommended against using malloc because it was incompatible with Windows real mode. In C/C++ version 7.0, malloc is designed for Windows protected-mode programming, and real mode is no longer a concern in Microsoft® Windows™ version 3.1. In most cases, calling _fmalloc is now better than calling GlobalAlloc directly.
_fmalloc is better than GlobalAlloc because of subsegment allocation. Instead of calling GlobalAlloc directly for each memory request, _fmalloc tries to satisfy as many requests as possible with only one GlobalAlloc call, and uses GlobalReAlloc to increase the size of a segment. Reducing the calls to GlobalAlloc cuts down on the overhead, time, and selectors required by an application.
Reducing selectors is particularly important for C++ programs. Most programs allocate lots of small objects on the heap. If new called GlobalAlloc directly, each small object would use a selector, and the program would reach the system limit of 8192 selectors (4096 in standard mode) too quickly.
Although _fmalloc is fine and dandy, _nmalloc is not nearly (no pun intended) as sophisticated. _nmalloc allocates fixed memory with LocalAlloc directly, which may result in memory fragmentation in the local heap. _nmalloc performs no subsegment allocation scheme, and the local heap must share a maximum of 64K with the stack and static data.
Here's another gotcha: _nmalloc is the default for the new operator in the medium and small models. _nmalloc allocates its memory from the local heap and must share the heap with the static data and stack—so a lot of things compete for only 64K of space. It is rather easy to run out of memory in the local heap. For example, a simple phone book that requires 200 bytes of data per entry would be able to store a maximum of only 330 names.
Heap Walker can help you determine the source of memory allocation. Memory allocated with LocalAlloc (through _nmalloc) expands the segment labeled DGroup. Memory allocated with GlobalAlloc (through _fmalloc) is labeled as a private segment.
For more information on _fmalloc, see the "Allocating Memory the Old-Fashioned Way: _fmalloc and Applications for Windows" technical article on the Microsoft Developer Network CD (Technical Articles, C/C++ Articles).
Many developers want to overload the new operator as soon as they learn that new calls _nmalloc. You can overload the new operator to perform specialized memory management, but overloading new to call _fmalloc instead of _nmalloc will not work.
The new operator has four versions. In this article, we are concerned only with the following two:
void __near *operator new( size_t size );
void __far *operator new( size_t size ) ;
In small and medium models, the compiler calls the near version of the new operator, and this version then calls _nmalloc. If we try to overload this function by calling _fmalloc, we would get a far-to-near pointer conversion error:
void __near *operator new( size_t size )
{
return _fmalloc(size);
//ERROR: Lost segment in far/near conversion.
}
A memory management scheme that overloads the near version of the new operator can return only near pointers, so using GlobalAlloc or GlobalAllocPtr will not work either.
Overloading the new operator to call _fmalloc instead of _nmalloc is obviously not the answer.
Asking the proper question will lead to a useful solution. The proper question is: "How do I get the far version of the new operator compiled in my code?" There are three ways to do this:
The following sections describe each method in turn.
You can think of the ambient memory model as the default memory model. Normally, the ambient memory model of a class is identical to the data model you specify at compilation time. If the data model is near (for example, in small or medium models), the ambient memory model is near. You can specify the ambient memory model for a class explicitly by using __near or __far; for example:
class __far CFoo {
};
Using the new operator on the CFoo class, as defined above, allocates the CFoo object on the global heap using _fmalloc.
Note The ambient memory model of a class must be identical to the memory model of all of its base classes. For example, if your class inherits from a Microsoft Foundation class, your class must have the same memory model as the Foundation class. If you use small and medium memory models, the ambient memory model of a Foundation class is near. We discuss the large model in the "Large-Model Programs" section.
You can override the ambient memory model on a per-object-instance basis:
class CBar{
};
void main()
{
CBar __far *pBar = new __far CBar ;
}
At first glance, the code above looks very straightforward. However, nonstatic member functions have a hidden parameter called the this pointer. It is through the this pointer that an object instance references its data. If the member function is near, it expects the this pointer to be near. A far this pointer results in an error because a far pointer cannot be converted to a near pointer.
The following code generates an error because it cannot find a default constructor that returns a far this pointer:
class CBar{
public:
CBar();
};
CBar::CBar()
{
}
void main()
{
CBar __far *pBar = new __far CBar ;
// ERROR C2512: 'CBar': An appropriate
// default constructor is not available.
}
To compile the code above, you must override the constructor based on the addressing type. This results in the following correct code:
class CBar{
public:
CBar();
CBar() __far ;
// Overload the constructor to take far this pointers.
};
CBar::CBar()
{
}
// Overloaded constructor.
CBar::CBar() __far
{
}
void main()
{
CBar __far *pBar = new __far CBar ;
}
Only functions that are actually called through a far pointer need to be overridden.
class CBar{
private:
int value;
buildIt() __far {}; // Must be far: CBar() __far calls it.
public:
CBar();
CBar() __far ;
// Overload the constructor to take far this pointers.
// inc is called through a far pointer.
void inc() __far { value++; } ;
// dec is not called through a far pointer.
void dec() { value--; } ;
};
CBar::CBar()
{
buildIt() ;
}
// Overloaded constructor.
CBar::CBar() __far
{
buildIt() ;
}
void main()
{
CBar *npBar = new CBar ; // Allocated in default data segment.
CBar __far *pBar = new __far CBar ; // Allocated in global heap.
pBar->inc() ; // Far addressing.
npBar->dec() ; // Near addressing.
npBar->inc() ; // Converts near pointer to a far pointer.
}
The use of the __far modifier can make programs very difficult to understand and debug. For example, let's assume that the following code is compiled in the small or medium memory models:
class __far CFoo {
public:
CFoo() ;
~CFoo() ;
...
};
class CBar {
public:
CBar() ;
~CBar() ;
...
};
CFoo aFoo; // Allocated in a far data segment.
CBar aBar // Allocated in default data segment.
CFoo *pFoo // Far pointer.
CBar *pBar // Near pointer.
CFoo __near *npFoo ; // Near pointer.
CBar __far *fpBar ; // Far pointer.
main()
{
CFoo anotherFoo; // Allocated on stack
// (default data segment).
pFoo = new CFoo; // Allocated in global heap.
pBar = new CBar; // Allocated in default data segment.
fpBar2 = new __far CBar;
// Error: No appropriate default constructor.
npFoo = new CFoo;
// Error: Cannot convert from a far pointer to a near pointer.
npFoo = new __near CFoo ;
// Error: Cannot convert from a far class to a near class.
npFoo = &aFoo;
// Error : Cannot convert from a far pointer to a near pointer.
}
You can see how complex an application can get when it mixes near objects and far objects.
Again, Heap Walker can help you determine whether memory is being allocated in the default data segment or in the global heap.
For additional information on the new operator, see Chapter 5 of the Microsoft C/C++ version 7.0 Programming Techniques manual on the Microsoft Developer Network CD.
As we discussed in the previous section, mixing near and far addressing is even more of a nightmare in C++ than it is in C and can offset many C++ benefits such as ease of maintenance and readability. The solution is to use the large model.
Although the large model has not been recommended in the past, the combination of Microsoft C/C++ version 7.0 and Windows version 3.1 now makes large model the memory model of choice.
When a C or C++ program is compiled with the large memory model, malloc is mapped to its model-independent or far version known as _fmalloc. Because the new operator calls malloc, heap objects are allocated in global memory.
The two issues associated with using the large model involve speed and creating multiple instances. The time you save by not worrying whether an object is near or far can be used to run a profiler and to optimize the application, thus compensating for any speed losses caused by the large model.
The new /Gx option in C/C++ version 7.0 simplifies the creation of multiple-instance, large-model applications. Make sure to use the following compiler options:
/Gt65500 /Gx
Programs with multiple read/write data segments cannot have multiple instances. By default, the Microsoft C compiler places initialized and uninitialized static data in two separate segments. The compiler places each static object that is larger than or equal to 32,767 bytes into its own segment. The /Gx and /Gt options override this behavior.
The /Gx option forces all initialized and uninitialized static data into the same segment. The /Gt[n] option places any object larger than n bytes in a new segment. (n is optional, as indicated by the square brackets.) If n is not specified, it defaults to 256 bytes. If n is large (for example, 65,500 bytes), most objects remain in the default data segment.
Because a multiple-instance application can have only one read/write data segment, the application is limited to 64K for all statics, the local heap, and the stack. However, C++ promotes the use of the heap through the new operator, which allocates memory from the global heap instead of the local heap in the large model, so the 64K local heap limit should not be a problem. Moreover, multiple-instance, small-model and medium-model applications also have only one read/write data segment.
Warning A bug in Microsoft C/C++ version 7.0 causes the compiler to place uninitialized global instances of classes and structures in a far data segment (FAR_DATA) when the /Gx option is used, resulting in two data segments. For this reason, you must declare global class objects and structures as near.
To illustrate, most Microsoft Foundation Class Library programs have a global object declared as follows:
CTheApp theApp;
To get multiple instances of this program, you must change the line to:
CTheApp __near theApp;
We recommend that you use the NEAR define:
CTheApp NEAR theApp;
The EXEHDR utility determines the number of data segments a program contains. In the sample EXEHDR output below, the lines that detail the number of segments are underlined and appear in bold.
Microsoft (R) EXE File Header Utility Version 3.00
Copyright (C) Microsoft Corp 1985-1992. All rights reserved.
Module: NEWOPR
Description: newopr - demonstrates new operator in
medium v. large model
Data: NONSHARED
Initial CS:IP: seg 1 offset e392
Initial SS:SP: seg 4 offset 0000
Extra stack allocation: 1000 bytes
DGROUP: seg 4
Heap allocation: 0400 bytes
Application type: WINDOWAPI
Runs in protected mode only
no. type address file mem flags
1 CODE 00000600 0ff8f 0ff8f PRELOAD, (movable), (discardable)
2 CODE 00010a00 013b0 013b1 PRELOAD, (movable), (discardable)
3 DATA 00000000 00000 00038 PRELOAD, (movable)
4 DATA 00012000 0159f 01fee PRELOAD, (movable)
Exports:
ord seg offset name
1 1 e358 _AFX_VERSION exported
2 1 f718 ___EXPORTEDSTUB exported
The MAP file helps determine the data that is placed in the FAR_DATA segment instead of the default data segment. To get a MAP file, be sure to specify a MAP filename and the /MAP option on the link line. In the sample MAP file below, lines of interest are underlined and shown in bold.
Start Length Name Class
0001:0000 004CFH NEWOPR_TEXT CODE
.
.
.
0001:E378 01C17H _TEXT CODE
0002:0000 013B1H COMDAT_SEG1 CODE
0003:0000 00038H NEWOPR1_DATA FAR_DATA
0004:0000 00010H NULL BEGDATA
0004:0010 011E0H _DATA DATA
0004:11F0 00000H XIFCB DATA
.
.
.
0004:140E 0000CH IOB2E DATA
0004:141A 00004H CONST CONST
0004:141E 00008H HDR MSG
0004:1426 00163H MSG MSG
0004:1589 00015H PAD MSG
0004:159E 00001H EPAD MSG
0004:15A0 009B6H _BSS BSS
.
.
.
Origin Group
0004:0 DGROUP
Address Export Alias
0001:E358 _AFX_VERSION _AFX_VERSION
0001:F718 ___ExportedStub ___ExportedStub
Address Publics by Name
0001:7C52 ??0CArchive@@REC@PEVCFile@@IHPEX@Z
.
.
.
0003:0000 ?spaceholder@@3VCObArray@@E
0001:7BF2 ?Store@CRuntimeClass@@RECXAEVCArchive@@@Z
0002:01F0 ?TextOut@CDC@@RECHHHPFDH@Z
0003:000E ?theApp@@3VCTheApp@@E
.
.
.
Address Publics by Value
.
.
.
0002:1380 ?GetStartPosition@CMapPtrToWord@@RFCPEXXZ
0003:0000 ?spaceholder@@3VCObArray@@E
0003:000E ?theApp@@3VCTheApp@@E
0004:0004 rsrvptrs
.
.
.
0004:1FEE __end
0004:1FEE _end
Program entry point at 0001:E392
Multiple-instance, large-model programs that use the Microsoft Foundation classes must build special versions of the Microsoft Foundation Class Library using the /Gt and /Gx options. Use the following command line:
nmake MODEL=L TARGET=W DEBUG=1 OPT="/Gt65500 /Gx"
Warning This variant of the Microsoft Foundation Class Library has not been tested extensively by Microsoft.
For additional information on using large-model programs with Windows, see the "Programming at Large" technical article on the Microsoft Developer Network CD (Technical Articles, C/C++ Articles).
newopr is a rather simple application that demonstrates some of the issues presented in this technical article. newopr tries to allocate 128 blocks of memory, 1024 bytes per block. When newopr is compiled as a medium-model program, it cannot allocate 128 blocks because it runs out of memory in the default data segment. In fact, the Microsoft Foundation Class Library raises an exception when the new operator fails, and newopr handles this exception gracefully.
When newopr is compiled as a large-model program, it can allocate all 128 blocks because the memory is allocated from the global heap instead of the local heap.
The best way to use newopr is to compile it medium model, run it, and examine the heap with Heap Walker. Run NMAKE with the CLEAN option, and then compile large model. Run the large-model version, and re-examine the heap with Heap Walker.
The following parameters control how newopr gets built:
DEBUG=[0|1] | Setting of 1 enables debugging information. |
LARGE=[0|1] | Setting of 1 compiles newopr as a large-model program. |
MINST=[0|1] | Setting of 1 compiles with /Gt and /Gx options to allow multiple instances. LARGE must be set to 1. |
CLEAN | Deletes .exe, .res, and .obj files. |
Sample nmake command lines are shown below:
Command Line | Makes |
nmake | Medium-model version. |
nmake DEBUG=1 | Medium-model debug version. |
nmake LARGE=1 | Large-model version. |
nmake MINST=1 | Medium-model version. MINST is ignored. |
nmake LARGE=1 MINST=1 | Multi-instance, large-model version. Foundation class large-model library must be compiled with /Gx and /Gt for this to work. |
nmake DEBUG=1 LARGE=1 MINST=1 | Same as above, but enables debugging. |
As discussed in the "Allocating Memory the Old-Fashioned Way: _fmalloc and Applications for Windows" technical article on the Microsoft Developer Network CD, _fmalloc called from a DLL behaves differently than _fmalloc called from an application. If you call _fmalloc from a DLL, it calls GlobalAlloc with the GMEM_SHARE flag, which changes the ownership of the allocated memory from the calling application to the DLL.
Ownership determines when the system will clean up the memory:
The key point here is that memory owned by a DLL (for example, GMEM_SHARE) can exist even after your application exits. The Smart Alloc sample application, which accompanies "Allocating Memory the Old-Fashioned Way: _fmalloc and Applications for Windows," illustrates this issue.
A DLL owns the memory allocated as GMEM_SHARE from within the DLL (in C++ or C). A DLL also owns the memory allocated by new in the DLL. Determining when and where memory is allocated can become very confusing in C++.
The code samples below are from the owner sample application and its associated OWNERDLL.DLL.
The DLL contains the following class:
class __export CContainedClass{
public:
char aMessage[1024] ;
};
class __export CFooInDLL{
public:
CFooInDLL () ;
void yourString() ;
void myString();
CContainedClass aContainedClass ;
char aBuffer[1024] ;
char *aString ;
} ;
CFooInDLL::CFooInDLL()
{
aString = new char[1024] ;
}
/////// INLINE FUNCTION ////////
inline
void CFooInDLL::yourString()
{
if (aString)
delete aString ;
aString = new char[1024] ;
}
/////// OUTLINE FUNCTION ///////
void CFooInDLL::myString()
{
if (aString)
delete aString ;
aString = new char[1024] ;
}
The .EXE for the program contains the following code fragment:
// Code in .EXE
CFooInDLL aFoo;
void somefunc()
{
aFoo.yourString() ; // Now application owns aString.
aFoo.myString() ; // Now DLL owns aString.
aFoo.yourString() ; // Now application owns aString.
}
Given these code fragments (where the object is defined in a DLL and declared in an application), the following rules apply:
Therefore, the application owns the memory for aFoo.
Therefore, during the construction of the aFoo object, memory for aContainedClass is allocated, and aContainedClass is also located in the application's memory space.
In most cases, it is best to design classes exported from a DLL so that memory ownership will not bounce between the application and the DLL. Using the debug versions of the Foundation class libraries helps track this problem.
Memory ownership for CFooInDLL object
The problem of determining memory ownership is just one more reason not to export C++ class interfaces from a DLL. In most cases, it is much better to export a C interface from a DLL.
There is no need to override the new operator to make it compatible with the Windows environment. The new operator calls malloc. The model-independent version of malloc, _fmalloc, is designed to manage subsegment allocation under Windows.
However, in medium or small memory models, malloc calls _nmalloc instead of _fmalloc. _nmalloc allocates memory through LocalAlloc. The best way to get the new operator to call _fmalloc is to use the large memory model. The ambient memory model for a class can be specified or overridden for a class instance, but both of these methods can quickly lead to confusing and complex code.
The following technical articles on the Microsoft Developer Network CD (Technical Articles, C/C++ Articles) are good sources of information on memory management in C++:
We also recommend the Microsoft C/C++ version 7.0 Programming Techniques manual, also available on the Microsoft Developer Network CD. Chapter 5 of this manual discusses memory management in C++.