Universal Headers Gotcha
Volume Number: 11
Issue Number: 5
Column Tag: Compiler Settings
The Devil’s In The Details
Don’t get surprised by the Universal Headers
By Bill Karsh, billKarsh@aol.com
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
There is a particularly insidious problem in Apple’s Universal Headers. Whether you
are affected at all, and how devastating it is, depends upon how you use their headers in
your projects. Fortunately, the problem only affects 68K code generation. Here are
the details, and what you can do as a work-around until new headers become available.
Which Header Versions?
The problem appears in the headers I got with Think C/C++ version 7, although these
headers themselves do not seem to have a version number. The problem persists
through the latest headers I got with MetroWerks CodeWarrior 5. These are version
2.0a3. Undoubtedly, you are using an affected version (I think they all are).
Structure Alignment Problem
The problem is a structure alignment conflict. Most compilers today let you apply
some options regarding how structures are defined, throughout the whole of a project,
or while certain pragmas are in effect. For example, you find a pop-up menu in
CodeWarrior’s Processor Preferences panel, that let’s you select options called
“68K,” “68K 4-byte,” or “PowerPC.” You can also use pragma statements like
these:
#pragma options align=mac68k
#pragma options align=mac68k4byte
#pragma options align=native
#pragma options align=power
#pragma options align=reset
These can be used to override a globally set option. I believe that the last option stays
in effect until either the next alignment statement, or the end-of-file is encountered.
In Symantec C++, the Compiler Settings page has a radio button group called
“Struct Field Alignment,” that sets alignment options for a project.
Generally, the chosen alignment determines how much padding (extra space) is
inserted into structures between fields, and how much padding is appended to the end of
structures. Deciding which option you would want is a matter of trading-off between
compactness of the data (less space) and higher performance (fields aligned on natural
addresses for the field’s data-type). We won’t go into those decisions here. What is
immediately essential is understanding two things.
First, these options are available to you (settable in an options page or by
pragma statements) independently of whether you are generating PowerPC (ppc) code
or 68K code.
Second, structures defined by the Mac OS, that is, defined in Apple’s headers,
must have no extra padding in them. When the compiler comes across the definition of
any Mac OS structure, like GrafPort, BitMap, or whatever, the current alignment
option must be mac68k. If the current setting were anything else, your code, and the
system software, would disagree on the offsets to various fields in a GrafPort, for
example. That’s a disaster.
Inside Mac Headers
If you look inside virtually any Apple header, you will see one of the following
examples near the top of the file:
#if defined(powerc) || defined (__powerc)
#pragma options align=mac68k
#endif
or
#if GENERATINGPOWERPC
#pragma options align=mac68k
#endif
Near the bottom you will see one of:
#if defined(powerc) || defined (__powerc)
#pragma options align=reset
#endif
or
#if GENERATINGPOWERPC
#pragma options align=reset
#endif
The intent here is to force the alignment type to mac68k while any Mac OS
structures are defined. Then, align=reset puts the alignment back to whatever was in
effect before. Can you see the problem? The pragma statements are made conditional
upon what type of code is being generated, ppc or 68K. If you are generating ppc code
only, there is no problem. If you are generating 68K code, the pragmas are not
executed. You’ve got a bad problem in 68K code if, when the headers are compiled, the
current alignment option is not mac68k. The pragma statements in the Apple headers
were not supposed to be conditional upon anything. I’m guessing here, but it looks like
a clear case of macro-mania-a-go-go.
Why Didn’t I See That?
I know, right now you are saying “how can any of my code work at all if this is true?
Why didn’t I catch this immediately!?” There are several sneaky ways this problem
might have escaped your notice thus far. For one thing, 68K compilers typically ship
with mac68k as the default alignment option. If you never changed that, you would
have been spared. Another possibility involves precompiled headers.
Your environment was shipped with precompiled headers. That is, many, but not
all, of the Apple headers were precompiled at the factory into a giant header called
MacHeaders68K (CW) or MacHeaders (Think), or something similar. Such a file is
usually included in a global preferences dialog. That would be the Language panel in
CW, or the Prefix page in Think’s dialog. There may be separate precompiled headers
for C and C++. Again, chances are that you never removed it. Back at the factory,
they compiled these precompiled files using the mac68k setting. Therefore, all the
structures defined in there are fine. However, not all of the Apple headers are
typically included in these things, only the most commonly used. This keeps symbol
table sizes down, and speeds compile times.
Any time you find a need to explicitly include an Apple header in your sources,
and it’s not among those precompiled at the factory, you are letting yourself in for it.
You have to do something to make sure that when the compiler reads that included
header, the alignment is mac68k. You have to take these steps yourself because the
provision to do this automatically in the headers themselves is incorrectly coded.
What Do I Do?
If you understand what’s happening, you can choose whatever method you like to adjust
for this; whatever works and is convenient. Let’s suppose you need Timer.h, and it’s
not currently precompiled. Here are some possibilities:
Re-precompile MacHeaders68K, or whichever variant your project needs.
Uncomment Timer.h in the source file that generates the precompiled header. Make
sure the project’s global option is “68K” (CW) or “Align to 1 byte Boundary”
(Think). Precompile a new MacHeaders, and include it in your working project’s
preferences dialog.
Include Timer.h in a source file like this:
#pragma options align=mac68k
#include
#pragma options align=reset
Repair the Apple header directly. Change this:
#if GENERATINGPOWERPC
#pragma options align=mac68k
#endif
to this:
#pragma options align=mac68k
Make the alignment options unconditional, as they were supposed to be. Do this for the
reset lines too!
I know this will save you days of debugging and thousands of dollars of
development costs. Could you send me just one dollar as a thank you? Worth a try.