I just found an insidious and disconcerting characteristic of the uVision3 ARM compiler V3.12a. I have inherited C code to migrate to the ARM processor. This C code uses unsigned char pointers quite liberally to pass the address of data back and forth. The code, of course, casts these generic unsigned char pointers to various data types to access the underlying data. I have found that if the unsigned char point happens to be pointing at a odd address and it is cast to a short type pointer (e.g., "*(SHORT*)p"), the compiler will resolve the address the previous even address. For a simplistic example, if the address of unsigned char *p happens to be 0x5 and the following code is executed:
unsigned char *p; ... *(unsigned short*)p = 0;
It is a very bad habit many PC i..... eh, programmers have to cast character pointers to longer field types. That blow right up in your face if you work with a processor where word (or longer) alignment is implememnted. Many processors will only access 16 bits if the address is divisible by 2, 32 bits if the address is divisible by 4 .... This lead to very efficient processor architecture, but is not "backwards comaptible to the 1990 PC" Erik
If you'll forgive the intrusion, since I am not Keil folk... http://www.aleph1.co.uk/armlinux/book/afaq.html You could also look at the Linux kernel sources. It has a facility to 'fixup' alignment errors at runtime (of course, with a small performance hit when it does the fix). Perhaps you could adapt the method for your system.
The alignment restriction is a characteristic of the ARM processor, not the compiler. (At least, not directly; the compiler is of course obliged to follow the dictates of the target CPU architecture.) Many other processor architectures (MIPS, SPARC, 68000 before 68020) require aligned data. The x86 or PowerPC architectures do not require aligned data, though they run faster when the data is aligned. (Misaligned accesses require two memory accesses instead of one.) Hence the reason that the code you inherited works on its original platform. However, the code is not correctly written for portability. Per the C standard, attempting to access a misaligned object is "undefined behavior", which means that the system can do anything it finds convenient. The casts override the compiler's tracking of types and their alignment requirements. That is, after all, exactly what a C cast does. It tells the compiler, "I'm the programmer; I know what I'm doing; do what I say". If the programmer is doing the wrong thing with his casts, it's hardly the compiler's fault. Misalignment detection is handed by the memory subsystem on the ARM, rather than by the core itself. If your ARM has ARM's MMU or "Memory Protection Unit" (MPU), then those peripherals will generate an interrupt to the core when they detect a misaligned transfer. You could write an interrupt handler to detect this condition, do a couple of aligned transfers, and patch the data back together (just as x86s or PPC does in hardware). But of course you'll lose a fair amount of speed if the misaligned accesses are common. On the other hand, this solution will cover all of the uses. It might be possible for you to force all your data to be aligned. In the worst case, you tell the linker that every single variable must be padded to a quad-byte boundary. Then, no matter what, the char* will be properly aligned for any data type. You might be able to get away with 2-byte alignment. You'll also need to set the compiler options to allow the compiler to pad structures. This will use more space in your RAM, but you won't have to check each use or rewrite the code. If you have structures that define some sort of packed message format, say for exchange with another processor, and fields in those structures are misaligned, then you're going to have to parse the received data byte-by-byte.
general on aligned architectures: 1) even if not aligned, treat as aligned (speed and portability) 2) arrange structures so that first all longs, than all shorts, then all chars. that way you get minimum padding 3) arrange variables (the global block and the block for each function) the same way for the same reason. OR waste all the memory you want and just align where and as needed. Erik
As an aside, doesn't ANSI 'C' recommend (require?) that you use void* rather than char* for a "generic" pointer? (Note that Keil C51 uses the term "Generic Pointer" with a different meaning...)
"The casts override the compiler's tracking of types and their alignment requirements. That is, after all, exactly what a C cast does. It tells the compiler, "I'm the programmer; I know what I'm doing; do what I say". If the programmer is doing the wrong thing with his casts, it's hardly the compiler's fault. Misalignment detection is handed by the memory subsystem on the ARM, rather than by the core itself. If your ARM has ARM's MMU or "Memory Protection Unit" (MPU), then those peripherals will generate an interrupt to the core when they detect a misaligned transfer. You could write an interrupt handler to detect this condition, do a couple of aligned transfers, and patch the data back together (just as x86s or PPC does in hardware). But of course you'll lose a fair amount of speed if the misaligned accesses are common. On the other hand, this solution will cover all of the uses." However, it appears that the compiler is not blindly obeying the programmer when he uses a cast, instead it is 'correctly' aligning the pointer to prevent an alignment exception occurring. The result of this is that memory is being silently trashed. In my opinion this is undesirable - I'd far rather my program crashed straight away than ran incorrectly.
As I stated, this is inherited code Then let me be the one to state it's not worth the electrons it's stored in, as far as porting it to other platforms is concerned. Odds are that porting this stuff will not be noticeably easier or cheaper than rewriting it from scratch. and a major re-write to change the way pointers are deployed is simply not an option Then I'm afried you've got yourself solidly stuck up that suspiciously brown creek without a canoo. That code you inherited was begging for trouble since its inception, and by porting it to another platform, its prayers have finally been heard. That code will not run on ARM without a major rewrite. So either you re-write, or you forget about using ARM as the platform.
and a major re-write to change the way pointers are deployed is simply not an option Then I'm afried you've got yourself solidly stuck up that suspiciously brown creek without a canoe Absolutely. I can not count the times I have heard or seen "is simply not an option" and "make it work" about the same issue. I recall once in my younger days before I dared to say NO to the brass, I told management that some hardware had to be redone to make the software work. I spent 2 months telling management daily "I need new hardware" before they agreed. When, as a result of this, the project was delayed, it was, of course, my fault. The most often occuring issue in this respect is that someone will tell management "this will take 6 months" and management say "we need it in 3". The young engineer afraid of losing his job will say "OK" and finish the project in 9 months. Erik
and a major re-write to change the way pointers are deployed is simply not an option Well, if you are willing to risk losing a lot of time trying this because I neither can nor will guarantee that this will work: Do a global search on "long" and all derivatives thereof and insert an align statement before each and every one. Then do the same for short and int and whatever else you can find more than 8 bits long (such as float). Then, of course you will have to do the same before all arrays and structures and then finally use the debugger to find what you missed and fix that. Erik
"Odds are that porting this stuff will not be noticeably easier or cheaper than rewriting it from scratch." At least it won't be a complete re-write from scratch - you have the old stuff as a reference for what to write, if not how to write it! "That code you inherited was begging for trouble since its inception, and by porting it to another platform, its prayers have finally been heard." And who knows what other nasties might be lurking? Once you've sorted out the pointers, you might start to see byte-ordering problems, packing problems, or who knows what other non-portabilities... "a major re-write to change the way pointers are deployed is simply not an option" Not an option - a necessity?! As you re-write this, be sure to do it in such a way that it still works on the original platform(s)!
"And who knows what other nasties might be lurking? Once you've sorted out the pointers, you might start to see byte-ordering problems, packing problems, or who knows what other non-portabilities..." I've heard tell that at Sun they had "lint parties".
Running lint on the project code would provide some measure of just how bad the code is and could be used as the basis for choosing between fix and rewrite.
From http://www.gimpel.com/html/reviews.htm ... "ALOA (short for A Lint Output Analyzer) is a tool that processes output generated by PC-lint and computes various useful metrics that give a quick overview of the internal quality of any C/C++ project. Furthermore, it shows which kind of Lint issues are most frequently encountered and highlights issue-laden modules. The metrics produced by ALOA are useful for tracking a project's lint compliance and for fine-tuning Lint policies."
Thanks all for your comments, pessimistic though they are. The project is a mess, to be sure, but time and, more importantly, politics forces my hand. Right now, I have a classic kludge solution for my alignment problem. If I should be so very lucky that's the last major problem, we might pull this off yet (very big if there). Doug
I've been on vacation (hence the delay in this response) but I do want to relay an acceptable technique I have found to solve my alignment problems, should any other unfortunate programmers be forced to work with inherited code, such as is our case. As stated previously, the problem is casting generic unsigned char (8-bit) pointers to larger data types, such as this:
unsigned char *p; ... *(__packed unsigned short*)p = 0;