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
  • 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;
    	...
    	*(unsigned short*)p = 0;
    
    Fortunately, the Kiel compiler offers a solution in the form of the keyword __packed, which forces byte alignment of any data object.

    The above example may or may not work, depending if the address of pointer "p" happens to be compiled to an even or an odd address. The following revision will force it to work regardless of the address:
    unsigned char	*p;
    	...
    	*(__packed unsigned short*)p = 0;
    

    This solution requires some careful editing, which at this point is definitely preferable to re-writing the code itself (I'll save that for later, hopefully).

Reply
  • 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;
    	...
    	*(unsigned short*)p = 0;
    
    Fortunately, the Kiel compiler offers a solution in the form of the keyword __packed, which forces byte alignment of any data object.

    The above example may or may not work, depending if the address of pointer "p" happens to be compiled to an even or an odd address. The following revision will force it to work regardless of the address:
    unsigned char	*p;
    	...
    	*(__packed unsigned short*)p = 0;
    

    This solution requires some careful editing, which at this point is definitely preferable to re-writing the code itself (I'll save that for later, hopefully).

Children
  • "This solution requires some careful editing,..."

    I recommend you use a macro, with conditional compilation, and continually re-check that it still builds (and works!) on the previous target:

    #ifdef __CA__
    // Keil CARM Compiler
    #define UNALIGNED __packed
    #else
    // The "other" compiler
    #define UNALIGNED_UNSIGNED_SHORT
    #endif
    
    :
    
    *(UNALIGNED unsigned short*)p = 0;
    Of course, it'd also be better to use a specific typedef of known size, rather than just rely on the implementation-dependent 'short'...