NULL peculiarities

I have a question about the behavior of some code I have written:

Packet Packet_str;

when I declare this structure in my main program and then check its memory location via a printf statement and hyperterminal it says it is 0000. This seems to cause problems in the program I am guessing that something thisnk that this is NULL, but... when i check to see if it is NULL or even (void *) 0 it is false but is true only when I check if it is 0x0000. One way I have been able to get around this is to create a variable which i do not use simply to make Packet_str at a different address than 0000. I have included the following code to help better understand my problem.

Packet_str gets passed to this function which uses it under the var name MyPacI. Buffer is an unsigned char * in the Packet structure. Also Packet_str is passed in by reference.

if( MyPacI->Buffer == NULL)/*false*/
printf("1:Unable to malloc memory!\n");
else if(MyPacI->Buffer == (void *) 0)/*false*/
printf("2:Unable to malloc memory!\n");
else if(MyPacI->Buffer == 0x0000)/*true*/
printf("3:Unable to malloc memory!\n");

the above is true when I do not make the dummy variable in main. If I make a Packet *dummy variable it offsets the Packet_str to memory location 0003 and all works correctly, but... if in main I set dummy = NULL it acts as if I never created the dummy variable. I only set dummy to NULL to avoid optimization on other compilers. It is not needed under keil. In short I am wondering why this all happens. Why do functions assume 0000 = NULL but not vise-versa, and is there another way to make Packet_str not be at address 0000 other than making an unused variable?

Parents

  • The problem here lies in the fact that the 8051 has a number of different address spaces (code, data, xdata). The Keil compiler supports both memory specific pointers (e.g., "U8 xdata* p"), which can only point to the declared address space, and 'generic' pointers ("U8* p"), which can point to any address space.

    To implement the generic pointer, Keil has to add a tag byte to the pointer to indicate which address space is pointed to. So, a generic pointer is three bytes long: tag + 16 bits of address. An xdata* is just 16 bits long, and a data* can be a mere 8 bits.

    If your definition of NULL is ((void*)0), then NULL is a generic pointer, and thus three bytes long. It will be 0x000000. A tag byte value of zero means data space, so this is really a pointer to i:0000.

    Xdata uses a tag value of 1. (Actually, it's the bank number + 1, if you have far memory.) A variable that lives at offset 0 in xdata would thus have a generic pointer value of 0x010000.

    So, ((void xdata*)0) != ((void*)0), because 0x010000 != 0x000000.

    The comparison directly with 0 is a comparison with a integer, not a pointer. The xdata pointer must thus be implicitly converted to an integer; a memory-specific pointer doesn't have a tag byte to worry about.

    So, ((void xdata*)0) == 0x0000.

    It may seem as though, in a specific-versus-generic comparison like this, that you could just ignore mismatched tag bytes, or force the generic tag to match the specific one. Using that rule would lead to false positives, though. Consider:

    U8 data d _at_ 0;
    U8 xdata x _at_ 0;
    U8 *g;
    
    g = &d;
    if (g == &x) // true, if you ignore the tag
       {
       *g = 0; // not what you'd expect
       }
    

    So I can't quite call the behavior a bug, though I can't say I'm tremendously pleased.

    If NULL were actually a C keyword, and not a #define convention, then the compiler could generate the appropriate code based on the other type involved. Keil could perhaps invent an "unspecified" memory type, just so that you could declare NULL as ((void any*)0) and have that value match any other tag value. The keyword seems pretty useless (if not harmful) in any other context, though.

    To be safe, you might want to stick to a single memory class for your pointer manipulation, and declare all the pointers explicitly of that memory type. You could then #define NULL appropriately, or add a #define XNULL ((void xdata*)0) for your own use.

Reply

  • The problem here lies in the fact that the 8051 has a number of different address spaces (code, data, xdata). The Keil compiler supports both memory specific pointers (e.g., "U8 xdata* p"), which can only point to the declared address space, and 'generic' pointers ("U8* p"), which can point to any address space.

    To implement the generic pointer, Keil has to add a tag byte to the pointer to indicate which address space is pointed to. So, a generic pointer is three bytes long: tag + 16 bits of address. An xdata* is just 16 bits long, and a data* can be a mere 8 bits.

    If your definition of NULL is ((void*)0), then NULL is a generic pointer, and thus three bytes long. It will be 0x000000. A tag byte value of zero means data space, so this is really a pointer to i:0000.

    Xdata uses a tag value of 1. (Actually, it's the bank number + 1, if you have far memory.) A variable that lives at offset 0 in xdata would thus have a generic pointer value of 0x010000.

    So, ((void xdata*)0) != ((void*)0), because 0x010000 != 0x000000.

    The comparison directly with 0 is a comparison with a integer, not a pointer. The xdata pointer must thus be implicitly converted to an integer; a memory-specific pointer doesn't have a tag byte to worry about.

    So, ((void xdata*)0) == 0x0000.

    It may seem as though, in a specific-versus-generic comparison like this, that you could just ignore mismatched tag bytes, or force the generic tag to match the specific one. Using that rule would lead to false positives, though. Consider:

    U8 data d _at_ 0;
    U8 xdata x _at_ 0;
    U8 *g;
    
    g = &d;
    if (g == &x) // true, if you ignore the tag
       {
       *g = 0; // not what you'd expect
       }
    

    So I can't quite call the behavior a bug, though I can't say I'm tremendously pleased.

    If NULL were actually a C keyword, and not a #define convention, then the compiler could generate the appropriate code based on the other type involved. Keil could perhaps invent an "unspecified" memory type, just so that you could declare NULL as ((void any*)0) and have that value match any other tag value. The keyword seems pretty useless (if not harmful) in any other context, though.

    To be safe, you might want to stick to a single memory class for your pointer manipulation, and declare all the pointers explicitly of that memory type. You could then #define NULL appropriately, or add a #define XNULL ((void xdata*)0) for your own use.

Children
More questions in this forum