Holy cow, I wrote a book!
I think it is a trait common to many people that they are fascinated by how their country is viewed by others. The leftist Die Tageszeitung from Germany reacted to the result of the most recent U.S. presidential election with their list of one hundred things that are still better in the United States.
Those who cannot read German can use a translation provided by a Metafilter reader (which is where I found this article, if you haven't figured it out). But of course it's better to read something in its original language if you can.
With the advent of the 80286, Windows could take advantage of
that processor's "protected mode".
processor. There was still no virtual memory, but you did have
memory protection. Global handles turned into "descriptors", more
commonly known as "selectors".
The 80286 did have support for both a "local descriptor table" and a
"global descriptor table", thereby making it possible to
have each process run in something vaguely approximating
a separate address space, but doing so would have broken Windows 1.0
compatibility, where all memory was global.
Addresses on the 80286 in protected mode consisted of a selector and an
offset rather than a segment and an offset. This may seem like a trivial
change, but it actually is important because a selector acts like a
handle table in hardware.
When you created a selector, you specified a whole bunch of attributes,
such as whether it was a code selector or a data selector, whether it
was present or discarded, and where in memory it resided.
(Still no virtual memory, so all memory is physical.)
GlobalAlloc() now returned a selector. If you wanted to, you could just
use it directly as the selector part of an address. When you loaded
a selector, the CPU checked whether the selector was present, discarded,
Since memory accesses were now automatically routed through the descriptor
table by the hardware, it meant that memory could be moved around
with relative impunity. All existing pointers would remain valid
since the selector remains the same; all that changes is the internal
bookkeeping in the descriptor table that specified which section of memory
the descriptor referred to.
For compatibility with Windows 1.0, GlobalAlloc() continued to emulate
all the moveability rules as before. It's just that the numeric value
of the selector never really changed any more.
(And please let's just agree to disagree on whether
backwards compatibility is a good thing or not.)
Next time, transitioning to Win32.
Once upon a time, there was Windows 1.0. This was truly
The Before Time. 640K. Segments. Near and far pointers.
No virtual memory.
Since there was no virtual memory, swapping had to be done
with the co-operation of the application. When there was an
attempt to allocate memory (either for code or data) and insufficient
contiguous memory was available, the memory manager had to perform
a process called "compaction" to make the desired amount of
contiguous memory available.
When you allocated memory via GlobalAlloc(), you first had to decide
whether you wanted "moveable" memory (memory which could be shuffled around
by the memory manager) or "fixed" memory (memory which was immune from
motion). Conceptually, a "fixed" memory block was like a moveable block
that was permanently locked.
Applications were strongly discouraged from allocating fixed
memory because it gummed up the memory manager. (Think of it as the
memory equivalent of an immovable disk block faced by a defragmenter.)
The return value of GlobalAlloc() was a
handle to a global memory block, or an HGLOBAL. This value was useless
by itself. You had to call GlobalLock() to convert this HGLOBAL into
a pointer that you could use.
GlobalLock() did a few things:
Applications were encouraged to keep global memory blocks
locked only as long as necessary in order to avoid fragmenting the heap.
Pointers to unlocked moveable memory were forbidden since even the
slightest breath -- like calling a function that happened to have been
discarded -- would cause a compaction and invalidate the pointer.
Okay, so how did this all interact with GlobalReAlloc()?
It depends on how the memory was allocated and what its lock state was.
If the memory was allocated as "moveable" and it wasn't locked,
then the memory manager was allowed to find a new home for the memory
elsewhere in the system and update its bookkeeping so the next time somebody
called GlobalLock(), they got a pointer to the new location.
If the memory was allocated as "moveable" but it was locked,
or if the memory was allocated as "fixed",
then the memory manager could only resize it in place.
It couldn't move the memory either because
(if moveable and locked) there were still outstanding pointers
to it, as evidenced by the nonzero lock count, or
(if fixed) fixed memory was allocated on the
assumption that it would never move.
If the memory was allocated as "moveable" and was locked,
or if it was allocated as "fixed", then you can pass the GMEM_MOVEABLE
override the "may only resize in place" behavior, in which case
the memory manager would attempt to move the memory if necessary.
Passing the GMEM_MOVEABLE flag meant, "No, really, I know that
according to the rules, you can't move the memory,
but I want you to move it anyway. I promise to take the
responsibility of updating all pointers to the old location to point to
the new location."
(Raymond actually remembers using Windows 1.0.
Fortunately, the therapy sessions have helped tremendously.)
Next time, the advent of selectors.
If you read through old code, you will often find casts
that seem redundant.
SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"string");
Why was "string" cast to LPSTR?
It's already an LPSTR!
These are leftovers from 16-bit Windows.
Recall that in 16-bit Windows, pointers were near by default.
Consequently, "string" was a near pointer to a string.
If the code had been written as
SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)"string");
then it would have taken the near pointer and cast it to a long.
Since a near pointer is a 16-bit value, the pointer would have been
zero-extended to the 32-bit size of a long.
However, all pointers in window messages must be far pointers
because the window procedure for the window might very well be implemented
in a different module from the sender.
Recall that near pointers are interpreted relative to the default
selector, and the default selector for each module is different.
Sending a near pointer to another module will result in the
pointer being interpreted relative to the recipient's
default selector, which is not the same as the sender's
The intermediate cast to LPSTR converts the near
pointer to a far pointer, LP being the Hungarian
prefix for far pointers (also known as "long pointers").
Casting a near pointer to a far pointer inserts the previously-implied
default selector, so that the cast to LPARAM captures
the full 16:16 far pointer.
Aren't you glad you don't have to worry about this any more?
The GlobalAlloc function has a GMEM_SHARE flag.
What was it for?
In 16-bit Windows, the GMEM_SHARE flag controlled whether the
memory should outlive the process that allocated it.
By default, all memory allocated by a process was automatically
freed when that process exited.
Passing the GMEM_SHARE flag suppressed this automatic cleanup.
That's why you had to use this flag when allocating memory to be
placed on the clipboard or when you transfer it via OLE to
another process. Since the clipboard exists after your program
exits, any data you put on the clipboard needs to outlive the
program. If you neglected to set this flag, then once your program
exited, the memory that you put on the clipboard would be cleaned up,
resulting in a crash the next time someone tried to read that data from
(The GMEM_SHARE flag also controlled whether the memory could be
freed by a process other than the one that allocated it.
This makes sense given the above semantics.)
Note that the cleanup rule applies to global memory allocated
by DLLs on behalf of a process. Authors of DLLs had to be careful
to keep track of whether any particular memory allocation was
specific to a process (and should be freed when the process exited)
or whether it was something the DLL was planning on sharing across
processes for its own internal bookkeeping (in which case it shouldn't
Failure to be mindful of this distinction led to
bugs like this one.
Thank goodness this is all gone in Win32.
According to the Constitution of the State of New Jersey, Article II, paragraph 6:
No idiot or insane person shall enjoy the right of suffrage.
Why are constitutional articles labelled with Roman numerals? Makes it sound like the Super Bowl or something.
The state appellate court did rule a few years ago that being hospitalized for psychiatric treatment does not constitute being insane for the purpose of determining voter eligibility.
Today is Election Day in the United States. Don't forget to vote! (Void where prohibited.)
Back in the days of 16-bit Windows, the difference was significant.
In 16-bit Windows, memory was accessed through values called
"selectors", each of which could address up to 64K.
There was a default selector called the "data selector";
operations on so-called "near pointers" were performed relative to
the data selector.
For example, if you had a near pointer p
whose value was 0x1234 and your data selector was 0x012F, then when
you wrote *p, you were accessing the memory at
(When you declared a pointer, it was near by default. You had
to say FAR explicitly if you wanted a far pointer.)
Important: Near pointers are always relative to
a selector, usually the data selector.
The GlobalAlloc function allocated a selector that could be used
to access the amount of memory you requested.
(If you asked for more than 64K, then something exciting happened,
which is not important here.)
You could access the memory in that selector with a "far pointer".
A "far pointer" is a selector combined with a near pointer.
(Remember that a near pointer is relative to a selector; when
you combine the near pointer with an appropriate
selector, you get a far pointer.)
Every instance of a program and DLL got its own data selector,
known as the HINSTANCE,
which I described in an earlier entry.
The default data selector for code in a program executable
was the HINSTANCE of that instance of the program;
the default data selector for code in a DLL was the HINSTANCE
of that DLL.
Therefore, if you had a near pointer p and accessed it via
*p from a program executable, it accessed memory relative
to the program instance's HINSTANCE.
If you accessed it from a DLL, you got memory relative to your DLL's
The memory referenced by the default selector
could be turned into a "local heap" by calling the
LocalInit function. Initialing the local heap
was typically one of the first things
a program or DLL did when it started up.
(For DLLs, it was usually the only thing it did!)
Once you have a local heap, you can call LocalAlloc to allocate
memory from it.
The LocalAlloc function returned a near pointer relative to the default
selector, so if you called it from a program executable, it allocated
memory from the executable's HINSTANCE; if you called it from a DLL,
it allocated memory from the DLL's HINSTANCE.
If you were clever, you realized that you could use LocalAlloc to
allocate from memory other than HINSTANCEs. All you had to do
was change your default selector to the selector for some memory
you had allocated via GlobalAlloc, call the LocalAlloc function,
then restore the default selector. This gave you a near pointer
relative to something other than the default selector,
which was a very scary thing to have, but if you were smart and
kept careful track, you could keep yourself out of trouble.
Observe, therefore, that in 16-bit Windows, the LocalAlloc and
GlobalAlloc functions were completely different! LocalAlloc
returned a near pointer, whereas GlobalAlloc returned a selector.
Pointers that you intended to pass between modules had to be in the
form of "far pointers" because each module has a different default
selector. If you wanted to transfer ownership of memory to another
module, you had to use GlobalAlloc since that permitted the recipient
to call GlobalFree to free it. (The recipient can't use LocalFree
since LocalFree operates on the local heap, which would be the
local heap of the recipient - not the same as your local heap.)
This historical difference between local and global memory still
has vestiges in Win32.
If you have a function that was inherited from 16-bit Windows
and it transfers ownership of memory, it will take the form
of an HGLOBAL. The clipboard functions are a classic example
of this. If you put a block of memory onto the clipboard,
it must have been allocated via HGLOBAL because you are transferring
the memory to the clipboard, and the clipboard will call GlobalFree
when it no longer needs the memory.
Memory transferred via STGMEDIUM takes the form of HGLOBALs
for the same reason.
Even in Win32, you have to be careful not to confuse the local
heap from the global heap. Memory allocated from one cannot be
freed on the other. The functional differences have largely
disappeared; the semantics are pretty much identical by this
point. All the weirdness about near and far pointers disappeared
with the transition to Win32.
But the local heap functions and the global heap functions
are nevertheless two distinct heap interfaces.
I'm going to spend the next few entries describing some of the
features of the 16-bit memory manager. Even though you don't
need to know them, having some background may help you understand
the reason behind the quirks of the Win32 memory manager.
We saw a little of that today, where the mindset of the 16-bit
memory manager established the rules for the clipboard.
[Raymond is currently on vacation; this message was pre-recorded.]