Departments


We Have Mail


Dear C/C++ Users Journal:

I just bought the September 1994 issue of your magazine. On page 19, David Singleton has an article on Windows memory management. However, this article is so full of bugs as an article on this subject can possibly get. The first line of the article states that "It is generally recomended that programs developed to run under MS-Windows use the medium memory model." This is just plain wrong. The recomended memory model for Windows programs is large. Medium model was for real mode Windows.

But probably the worst error is the article's emphasis on GlobalLock and GlobalUnlock. These functions are only needed for real mode Windows, and as we all know, real mode Windows disappeared along with Windows 3.0. Windows 3.1 can move memory at any time without invalidating pointers. This, of course, is due to its use of a Local Descriptor Table (LDT) to translate selector:offset to a logical address.

In fact, Microsoft now recomends using malloc/free or new/delete (which maps to malloc/free) for memory allocations. Both Visual C++ and Borland C++, and probably the rest of the Windows C/C++ compilers, use a suballocation scheme in malloc, similar to the one presented in the article. But even though the article assumes real mode Windows, it actually requires protected mode to work. On page 25, FarHeapBlock::GetHbFromHandle is defined as follows:

FP_FHB FarHeapBlock::GetHbFromHandle(HGLOBAL h)
{
   FP_FHB p fhb = (FP FHB) GlobalLock (h);
   ASSERT (p_fhb != NULL);
   GlobalUnlock (h);
   return p_fhb;
}
The GlobalUnlock call actually tells Windows that it's okay to move the block. The pointer returned from the function is therefore invalid! It can be used safely only in standard or enhanced mode, and in real mode only until the next GetMessage or PeekMessage call. I find it amazing that a magazine starts off its Windows Programming issue with such an outdated and erroneous article. The only thing I got out of it was the name of a magazine to stop buying, and the name of a consultant firm to avoid.

Martin Larsson, author of several unknown utilities for DOS and Windows
e-mail: larsson@cs.colorado.edu

David Singleton replies:

Dear Editor,

Thank you for sending me a copy of Martin Larsson's letter. Interesting and provocative reading. I give my response below, interlaced with excerpts from Martin Larsson's original text [in italics]. In the interests of courtesy, I am copying this reply to Larsson. If he would like to engage in further dialog direct with me, I would be delighted to respond.

The recomended memory model for Windows programs is large. Medium model was for real mode Windows.

I would ask Larsson to justify his statement that the recommended model for Windows Programming is large. My justifications for my statement are (in no particular order of importance):

a. There are probably still some people out there using Windows 3.0. We do need to remember them.

b. The medium memory model allows one to run more than one instance of a program. You cannot do this with the large model. (I will willingly concede that there may be occasions when it might be disastrous to allow more than one instance of a program to run.)

c. The Visual C++ V1.5 AppWizard utility, when it builds a program framework, defaults to the medium memory model. It seems to me from this that Microsoft, themselves, endorse the use of the medium memory model as the preferred starting model.

d. Medium memory models are generally smaller and faster because near pointers can be used to access the data in the local data segment.

e. Finally, I would refer to Charles Petzold's book Programming Windows 3.1, published by Microsoft Press, Chapter 7, Page 285. His book is to all accounts something of an authority on Windows programming. Petzold says: "Windows programmers who require more than 64 KB of data in their programs might be feeling a little nervous at this point. They have a right to be because the compact and large models are not recommended for Windows programs. This does not mean they can't be used however."

But probably the worst error is the article's emphasis on Global Lock and Global Unlock.

Larsson has made a significant error in his statement concerning the use of GlobalLock. It is perfectly true that there is no longer the need to physically lock memory when using Windows 3.1. However, GlobalLock performs one essential additional function that Larsson seems to have forgotten. It translates the HGLOBAL returned by GlobalAlloc into a far pointer to the allocated memory. It is certainly Microsoft policy to use GlobalLock in conjunction with GlobalAlloc, as evidenced by the example code in Microsoft KnowledgeBase articles Q77226 and Q74197.

In fact, Microsoft now recomends using malloc/free or new/delete (which maps to malloc/free) for memory allocations.

If one is using a large memory model or a medium memory model with less than 64 KB of data, then, of course, one should use malloc/free or new/delete. It is by far the easiest way of getting more memory in these cases. However, if one wishes to use a medium memory model and have more than 64 KB of data, then one must use the Windows global memory services, as discussed in the article.

But even though the article assumes real mode Windows, it actually requires protected mode to work.

I regret that Larsson has again made a small error. I quote from the Microsoft SDK Programmers Reference Manual, Volume 2, concerning GlobalUnlock: "With movable or discardable memory, this function decrements the object's lock count. The object is completely unlocked and subject to moving or discarding if the lock count is decreased to zero."

The emphasis above is mine. The marked phrase is the key to answering Larsson's concern. Each FarHeapBlock holds references to the previous and next FarHeapBlocks in the chain of FarHeapBlocks. I decided to hold these references as HGLOBALs. To get the corresponding address, I needed to use GlobalLock with an equivalent GlobalUnlock (it is always necessary to use these as a pair) to get the corresponding address.

This is what the function FarHeapBlock:: GetHbFromHandle does for me. On entry to this routine, I would expect the relevant global heap lock count to be 1, as the HGLOBAL points to a block that has already been allocated. The call to GlobalLock will increase the lock count to 2. The subsequent call to GlobalUnlock will return the lock count to 1. As the function never causes the lock count to go to zero (and hence unlock the block), Larsson's comment is invalid

I look forward to seeing Martin Larsson's response to my points. I am prepared to stand by my article and I believe that the above responses have refuted all of his comments on the article.

Best wishes,

David Singleton
100265,3625@compuserve.com

Larsson replies:

Here's my reply to David Singleton's comments.

There are probably still some people out there using Windows 3.0. We do need to remember them.

So? The large model runs on Windows 3.0 too doesn't it? I will agree that Windows 3.0 has a bug (how can I not?) that pagelocks all but the first segment if you have multiple data segments. So, there can be no movement of the segments. Not good. However, with Windows 95 coming up, I don't think we should limit ourselves by bugs in Windows 3.0. It's as simple as saying, "The program runs on Windows 3.0 and greater. However, if you experience 'out of memory' errors on Windows 3.0, you might want to upgrade to Windows 3.1. Windows 3.0 has a bug that..." You get the idea.

Remember, this only applies to programs with multiple writeable data segments. And they can't use the medium model anyway. Also, remember that Windows is a large model program. So when you use the medium model, you're in fact dealing with mixed-model programming. This is, and has always been, a real pain since you have to remember which pointers are far and which are near.

The medium memory model allows one to run more than one instance of a program. You cannot do this with the large model.

This is simply not true. The limitation is that you cannot have more than one read-write segment, as long as all segments but one are read-only. Multiple instances works just fine in the large model (Programming at large, Dale Rogerson, Microsoft Developer Network Technology Group). MS-compilers from 8.0 and up (VC++ 1.0), combine your data into one segment if possible. This means that your large model program automaticaly can run multiple instances. Of course, Borland and Watcom always did this.

The Visual C++ V1.5 AppWizard utility, when it builds a program framework, defaults to the medium memory model. It seems to me from this that Microsoft, themselves, endorse the use of the medium memory model as the preferred starting model.

Read, "The C/C++ Compiler Learus New Tricks," by Dale Rogerson, written Aug. 28, 1992, revised Jan. 27, 1993. The article is available on The Microsoft Developer Network. The conclusions is that Microsoft C/C++ version 7.0 introduces new programming practices that facilitate the development of applications for Windows version 3.1 in protected mode. Programmers can now:

Medium memory models are generally smaller and faster because near pointers can be used to access the data in the local data segment.

Microsoft Systems Journal, 1993, Volume 8, Number 10, Questions & Answers, C/C++, last question, summary: "The upshot is: if you have a good C++ compiler that supports Windows, don't fret over memory management. But do use the large model... Of course, you can always get the memory in the medium model if you really want, by declaring everything far, and allocating everything with _fmalloc of farmalloc, but then you have all sorts of problems in C++ trying to allocate objects with new so their constructors get invoked.

Why subject yourself to such torture? Anyone who tells you that performance is slower in the large model is probably a frustrated assembly-language programmer, not to be trusted. It's true, of course, it is slower. But not much. Your users will probably never notice. If a particular section of your code is performance-critical, you can always use near pointers, or even rewrite it in assembly. The large model is simply the easiest way to get gobs of memory."

Yes, this is on C++, but most of it applies to plain C also. The large model just makes life so much easier.

Finally, I would refer to Charles Petzold's book Programming Windows 3.1, published by Microsoft Press, Chapter 7, Page 285. His book is to all accounts something of an authority on Windows programming.

I agree on one point, Petzold's book is a very good book on Windows programming. But, chapter 7 was not updated for 3.1. Therefore, it should not be accepted as the authority on Windows 3.1 memory management. I'm sorry, but Petzold's just plain wrong. Ask the guys on comp. os.ms-windows.programmer.memory what they think of Chapter 7 in Petzold's otherwise exellent book. You'll get the idea.

Larsson has made a significant error in his statement concerning the use of GlobalLock.

I guess I wasn't clear enough. My point is that there's no reason for locking and unlocking memory all the time. Lock once, when you allocate, using malloc, farmalloc, GlobalAllocPtr, or new. Unlock once, when you free the memory using free, farfree, GlobalFreePtr, or delete. Now, there are exceptions, just as with everything in Windows. For instance, you can allocate memory in a dialog procedure and return it to 'the calling function. However, since DialogBox only returns an integer, you'll have to return a handle (16-bit). You could of course use a global or member data in C++. Or you could send a message to the parent of the dialog.

As the function never causes the lock count to go to zero (and hence unlock the block), Larsson's comment is invalid.

So, what you're saying is that you call GlobalLock and GlobalUnlock totally unnecessary since you could just as well keep a copy of the pointer. Quite interesting when you're using the medium model to gain speed. And, you have memory locked all the time, quite contrary to what Petzold recommends. Of course, Petzold's wrong, memory can be locked for the whole duration of the program. But you seem to contradict yourself when you follow Petzold's advice on memory model, but not on when to lock and unlock the memory you allocate.

I am prepared to stand by my article and I believe that the above responses have refuted all of his comments on the article.

Well, I think I've made my points clearer. All references are to Microsoft Developer Network CD #8.

Martin Larsson

Singleton replies:

Dear Larsson,

Thanks for your last message. As the messages seem to be getting rather long, I will not go repeating everything. I will just repeat any necessary salient points.

So? The large model runs on Windows 3.0 too doesn't it? I will agree that Windows 3.0 has a bug... You get the idea.

Point noted, thanks

Remember, this only applies to programs with multiple writeable data segments. And, they can't use the medium model anyways.

Agreed.

So when you use the medium model, you're in fact dealing with mixed-model programming. This is, and has always been, a real pain...

Agreed. However, that is one nice thing about using MFC. Most of the time, one does not need to worry about near and far pointers, although there are, of course, occasions when one does need to remember which are which and that can, I agree, sometimes be a pain.

The limitation is that you cannot have more than one read-write segment, as long as all segments but one is read-only.

Correction accepted. That is really what I meant to say. In my article I was, though, dealing with the case where one would want more than one segment's worth of read-write data (i.e., more than 64 KB of data) and, in this case, my assertion that one cannot have more than one instance of a program is correct. Thanks for the references to the two Microsoft articles and for your comments on Chapter 7 of Petzold's books. I shall add them to my data bank.

My point is that there's no reason for locking and unlocking memory all the time.

I entirely agree with your comment. There is absolutely no need to lock and unlock memory all the time. You raised this comment with reference to my function FarHeapBlock::GetHbFromHandle. My reason for doing this is contained in the article, where I say, "I use doubly linked lists to keep track of individual FarHeapBlocks. Thus, a FarHeapBlock contains pointers both to the previous FarHeapBlock and to the next FarHeapBlock. In the case of FarHeapBlocks, I decided to store the HGLOBAL of the previous and next blocks, rather than a far pointer."

Whenever I need to recover the actual address of a FarHeapBlock, I call FarHeapBlock::GetHbFromHandle, using GlobalLock and GlobalUnlock to do the necessary conversion. The fact that they set and release locks is, from this function's perspective, a side-effect. What I want is the HGLOBAL to FP_FHB conversion.

The article then goes on to say, "With hindsight, I could have used far pointers instead. Indeed, this could probably give slightly more efficient code." I am sure you would say that I could delete 'probably' and 'slightly' from the above. With hindsight, I would agree with you.

I will close by thanking you for this fascinating discussion. It is always good scientific and engineering practice to test one's arguments by open debate. I too use the large model when I consider it appropriate. However, I also use the method set out in my article, when that is appropriate.

Best wishes,

David Singleton

In fairness to David Singleton, I should report that we sat on his article for a number of months before publishing it, then failed to check whether all the information was still timely. We indulge in such antics less and less often, and we check our accepted submissions better all the time, but we — and our authors — will never get it perfect.

Windows, in particular, is a bottomless pit of complexity. The preceding discussion reminds me of numerous debates from the distant past over how best to perform various operations under various System/370 operating systems. Microsoft has reinvented IBM a quarter century later. I cross my fingers every time one of our authors describes one side of this latest elephant. They are more courageous than I am. — pjp

Bill,

Re-October 1994 C/C++ UJ: yet another fine issue... I have mixed, mainly queasy, feelings over Bob Jervis's proposal for a (C++)--! It's true that C++ has grown like Topsy and even the experts are confused. (See, for example, the theological altercations in any issue of The C++ Report.) But surely all programming languages have had similar growing pains as we've struggled to gain familiarity with "arcane" (i.e., new) concepts and features. Take the C declaration syntax (please). And consider that after 20 years or so, we still see "not quite accurate" CUJ articles using or explaining C arrays-as-pointers. If one of Bob's motivations is to ease the compiler-writer's burden, forget it. Compiler-writers, in rerum natura, thrive on impossible challenges — specification ambiguities being especially welcomed (and provably unavoidable from the lessons of Algol 68). Otherwise, pjp would be editing The Machine-Language Users Journal. My suspicion is that as the C community mulls and fights over Bob's "minimalist" OOPL, his (C++)-- will post-accrete "one damn good thing after another,"aping the agonizing stepwise refinement (some might call it unfinement) of Bjarne's original "C with Classes" (1979) into ANSI/ISO C++ (1994). The story is uniquely documented in The Design & Evolution of C++ (Bjarne Stroustrup, Addison-Wesley, 1994), which traces the pros and cons of each increment. Consider just one item not mentioned by Bob: the C++ "exposure" specifier protected. This public/private "compromise" was added for members in Release 1.2 and for base classes in Release 2.1. For member functions, Bjarne still considers protected to be a "fine way of specifying operations for use in derived classes." However, protected data members are now frowned on, even by Mark Lipton who pushed for the change (ibid, p. 302). Will Bob exclude protected completely as "inessential," or, learning from C++ experience, confine it to member functions. I smell a good twelve months argufying on this point alone. It seems that (C++)-- will never "catch up" since "All is Flux," especially C++ itself. PAX, etc.

Stan Kelly-Bootle
Contributing Editor,
UNIX Review and OS/2 Magazine

PS: Re-your name: the Celtic diphthong au is usually pronounced ow, as in plow, not aw, as in plaudit. However, both sounds seem appropriate! BTW: Peter van Linden offers $1 for each error found in his Expert C Programming. I reported that he had spelled your name Plaugher on two occasions, but he only sent me one dollar!

Whew! It might have been easier refereeing the previous discussion on Windows programming than replying to a classic Kelly-Bootle missive. (C++)-- is a cute name, but an invalid expression, in Standard C at least. More important, not even Jervis is proposing making C a full-fledged OOPL. The idea is to crib a bit of prior art that has proven utility at low cost in complexity, not to invent yet another language on the fly as part of the standardization process.

As for my name, I'm told it originally hails from Alsace Lorraine. A slippery dipthong like au shifts neatly between French and German pronunciation, depending on who's in charge in a given year.

Dear Sirs!

My comments about the idea of the modifications to C mentioned in the article "All is Flux" (CUJ, October 1994 by B. Jervis): YES! YES! YES! YES! YES! YES! ... ad infinitum! The only problem with it: It may kill C++. Then again we (real everyday programmers) may need such extended C (maybe we should call C+-?) desparetely when C++ dies by itself under its own weight. C++ inflates at the rate approaching the rates of supernovas.

Sure, it gives more and more employment for language commentators, critics, academic programming gurus, etc. (This is not an innuendo about PJP. Please believe me!). Anyone remember Algol 68? If we needed a language so big as the future C++ maybe we should settle for something already working and tested like Ada? C became so popular because it allowed people to program close enough to the performance levels of assembly language with benefits of the structure of a higher-level programming language. C++ became popular only because it is perceived as an extension of C!

This is of course my personal view, but then again I am being told from time to time that true OOP can happen only in languages like Smalltalk and so on. B. Jervis! Go on!

Bogdan M. Baudis

Innuendo or no, I do get a certain job security from the ambitions of language designers. Still, I wish sometimes the job of explaining esoterica wasn't quite so hard. —pjp

C/C++ Users Journal

This is the second of two letters. The first, which is enclosed behind this one, was waiting to be stuffed in its envelope when my copy of the October issue of the Journal arrived. Needless to say, I read Bob Jervis's article and your sidebar with great interest. Having done that, I feel that what I said in my first letter concerning ADTs versus OOPing, and C's capacity to support new dialects, still needs saying — perhaps more than ever.

If classes are added to C, that's fine. That is, it is if they don't get in the way of their ADT forebears. My concern is that although there are many instances where another level of sophistication is needed, anything which creates the impression that OOPing is the way could stifle the growth of C as a source of new programming ideas. Also, if the freedom to create and expand ADT libraries is legislated against by committee fiat, I, for one, will not use any compiler implementing that standard.

I've studied more than a few articles over the years on how to OOP in C, including the one Colvin did in CUJ last year. I've also "poured over" more than one book on the subject, and I have to say that I've yet to find any well-wrought discussion of the relationship between ADTs and classes as clone factories. The nail still protrudes!

On a related point: In his Letter to the Editor in this October issue, someone named Marty says: "One of the best ways to become a better programmer is to read other people's code,..." This is a fairly common, often enunciated belief, but one which I find actually qualifies as a half-truth. Reading other people's code can also be one of the worst ways to learn. Not only are there many ways to write correct C, the ways that apear in print tend to perpetuate what might be called The-Old-Boys' version.

A full-fledged discussion of C's several prevailing styles and the effect they have on program semantics ought to make an excellent subject for one of your columnist's columns. It ought also to fit-in nicely with your expressed intent to cover further discussion of C's ongoing development.

Truly,

Mark Rhyner

Dear Mr. Plauger:

I would like to suggest that a "read_only" data member access specification be added to the public, protected, and private access specifiers in the C++ standard.

I find myself writing numerous get_XXX functions to allow users of my classes to access the private data in them. The data is declared private to localize the responsibility and maintain the integrity of the data, but I still wish the callers to be able to act on the data. The only other alternative I can think of is to make the data public, which is okay if the users are disciplined about not altering the data.

Problems with read_only might arise if the data exposed is a pointer, but that is the way of C anyway. If you have any suggestions on how to deal with this issue in C++ as it stands, I would be very interested.

On another subject, I would be very interested in reading a regular column in The C/C++ Users Journal on programming style. It could cover issues such as the above:

Sincerely,

David Rosenbush

I agree that read-only access to certain member objects could save some tedious writing. I also agree that style is an important topic. So far, we've kind of smeared responsibility for discussing issues of style among all our columnists. — pjp