This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Casting Char Pointers to Other Data Types

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;

rather than address 0x5 and 0x6 being set to zero as one would have expected, address 0x04 and 0x05 are set to zero and address 0x06 is left unchanged.

The same affect is demonstrated if, in the above example, the unsigned char pointer "p" is cast to an unsigned int, except is this case the compiler removes bits 0 and 1 from the address so that it is looking at divisible-by-four address. You could end up unintentionally nailing more nearby data this way.

If you are lucky and the pointer "p" lies on an even address boundary for short type casts of char pointers, the code works just fine, but odd address boundaries definitely are going to cause problems. Similar logic applies to larger standard data types. (Fortunately the code base does no casting to structure pointers, so that is not a problem for me now, but it may be a problem, too).

As I stated, this is inherited code and a major re-write to change the way pointers are deployed is simply not an option. Are there any other ideas out there? I know I can replace every pointer cast to other than an unsigned char with a function call to read or write the data, but frankly unless there is an intuitively obvious solution I'm not seeing, this looks like a huge hole in the compiler logic. Is there any hope for my faith in the Keil folks?

Thanks for any encouragement,
Doug

Parents
  • 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.

Reply
  • 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.

Children
  • 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

  • "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.