Universal Headers Gotcha
Volume Number: | | 11
|
Issue Number: | | 5
|
Column Tag: | | Compiler Settings
|
The Devils In The Details
Dont 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 Apples 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 CodeWarriors Processor Preferences panel, that lets 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 fields data-type). We wont 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 Apples 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. Thats 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. Youve 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. Im guessing here, but it looks like a clear case of macro-mania-a-go-go.
Why Didnt I See That?
I know, right now you are saying how can any of my code work at all if this is true? Why didnt 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 Thinks 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 its 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 whats happening, you can choose whatever method you like to adjust for this; whatever works and is convenient. Lets suppose you need Timer.h, and its 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 projects global option is 68K (CW) or Align to 1 byte Boundary (Think). Precompile a new MacHeaders, and include it in your working projects preferences dialog.
Include Timer.h in a source file like this:
#pragma options align=mac68k
#include <Timer.h>
#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.