We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
Specific pointer -> generic pointer -> boolean (i.e. implicit compare to 0) seems not to work as expected.
void main() { char* p; char* q; char xdata* xp = NULL; bit fail = FALSE; // This fails. if ( p = xp ) { fail = TRUE; } // This is ok. if ( ( q = xp ) == NULL ) { fail = TRUE; } }
A pointer to xdata location 0 isn't 0. It's 1'0000H. If the result of the assignment converts to integer, then it would be a non-zero value. I agree that such might not be what you would expect, undesirable even unto bugginess. It would perhaps be better if tagged pointers ignored the tag byte when converting to ints. But then you'd have a harder time dealing with far addresses. And a context-sensitive conversion would be confusing as well. How do you have NULL #defined? "((void*)0)"? Presumably that brings the type of the second expression back to pointer, including the tag, so the tag byte gets handled properly. (And would this definition be memory-model dependent, as there has to be a default location for that "void"?)
Yes, it should be an implicit conversion to int. If an explicit conversion is provided, all is ok, so I expect that it's a bug.
void main() { char* p; char xdata* xp = NULL; bit fail = FALSE; // This works. if ( ( int )( p = xp ) ) { fail = TRUE; } }
Yes, it should be an implicit conversion to int. Why do you think that? Implicit pointer conversions are discussed in the manual. http://www.keil.com/support/man/docs/c51/c51_le_ptrconversions.asp The compiler views the XDATA pointer conversion into a generic pointer as a 2-byte object implicit cast into a 3-byte object. The expression tested in the if statement is a 3-byte object which is definitely NOT NULL (since the upper byte (the memory selector) is a 1). These are the kinds of problems you run into when you use Harvard architectures. You could ligitimately say that there really are five NULL pointers on an 8051. There are NULL pointers for each of DATA, IDATA, XDATA, PDATA, and CODE. Jon
"You could ligitimately say that there really are five NULL pointers on an 8051. There are NULL pointers for each of DATA, IDATA, XDATA, PDATA, and CODE." Perhaps these could be provided in a system header file?
How about: #define TYPED_NULL(type) ((void type*)0) if (p == TYPED_NULL(xdata))... Or you could just define five of them with five names. Perhaps it might be better to #define IsNull(expr) ((expr) == (void*)0) to ensure that the conversion to pointer is always present. Not that you can be sure every expression uses the macro. Yet another use for typeof(), if it existed. Contact your local ISO C representative...
ISO C has nothing to do with this. The whole issue revolves about an explicitly non-ISO behaviour of pointer types added to the language by Keil. ISO C requires that comparisons of pointers to a literal zero treat that zero as a "null pointer constant", instead of treating the pointer as an integer. In other words, the Keil extensions manifestly violate standard C behaviour already. Asking for changes to ISO C won't change that fact at all. The necessary changes woudl be in Keil's treatment of memory-specific pointers.
You misunderstand me, Hans. typeof() is an often suggested addition to C and particularly C++ for any of a number of reasons. If such a thing existed, it would be useful for creating other pointers and constants of a type matching another type, guaranteed by the compiler. If I write #define IsNull(p) (p == NULL) only the programmer can make sure that NULL actually has the correct type. This thread exists because that's hard to do. #define IsNull(p) (p = (typeof p)0) would guarantee that the types of the two pointers match, regardless of changes in memory model or the declaration of p. This is indeed unnecessary in standard C, which assumes one flat address space. But we have to deal with Keil extensions for a processor that's anything but flat. So I was suggesting that this facility could be a useful, extended, way to deal with other useful, extended, features. I imagine typeof could also be useful for generating more efficient code. Currently, generic pointers in Keil are tagged, and dynamically checked at run-time on every use of the pointer. It's like RTTI for pointers. And if you don't know what type a pointer may be, then you have to promote to a generic pointer. Typeof() could allow moving some of this burden from run-time to compile-time, at the risk of cracking open the door to generic programming. I'm not suggesting that the ISO change C to include support for multiple address spaces in which to point.
If I write #define IsNull(p) (p == NULL) only the programmer can make sure that NULL actually has the correct type. This thread exists because that's hard to do. This situation actually is one which is defined to work perfectly fine, in ISO C. It's only Keil C, and particular it's memory-specific pointers, that make it hard. For standard-complying C compilers, comparing NULL or 0 to any pointer-valued expression is guaranteed to work correctly, without any further worry by the programmer. I'm not saying that typeof() wouldn't be useful, but that it shouldn't ever be needed for this particular usage, in the first place. Keil broke an important C semantic feature here, in the sense that the special memory-specific pointers don't support standardized behaviour. Given that attitude, I would say it's safe to assume they'ld ignore the rules about the hypothetical new typeof() operator just as completely as they currently ignore the rules about null pointer constants. The correct change would be to not handle (pointer == 0) or (pointer == NULL) by casting the pointer to some other type, but rather by casting the zero to the given pointer's type. The C compiler already has all the information it could possibly need for doing that, and the C standard says that's what it should do --- the only problem is that Keil doesn't.
#define IsNull(p) (p == NULL)
It's only Keil C, and particular it's memory-specific pointers, that make it hard. NULL is not difficult. It is the 8051 architecture that throws a wrench into things. The 8051 is not a vonNeumann architecture and it is not linear. In the 8051, you have:
strcpy (char *d, char *s);
strcpy_ii (char idata *d, char idata *s); strcpy_ix (char idata *d, char xdata *s); strcpy_ic (char idata *d, char code *s); strcpy_xi (char xdata *d, char idata *s); strcpy_xx (char xdata *d, char xdata *s); strcpy_xc (char xdata *d, char code *s);
#include <REG52.H> #include <stdio.h> void main (void) { char idata *ip = NULL; char xdata *xp = NULL; char code *cp = NULL; char *gp = NULL; SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr */ TMOD |= 0x20; /* TMOD: timer 1, mode 2, 8-bit reload */ TH1 = 221; /* TH1: reload value for 1200 baud @ 16MHz */ TR1 = 1; /* TR1: timer 1 run */ TI = 1; /* TI: set TI to send first char of UART */ if (ip == NULL) printf ("IP is NULL\n"); if (xp == NULL) printf ("XP is NULL\n"); if (cp == NULL) printf ("CP is NULL\n"); if (gp == NULL) printf ("GP is NULL\n"); if ((gp = ip) == NULL) printf ("(GP = IP) is NULL\n"); if ((gp = xp) == NULL) printf ("(GP = XP) is NULL\n"); if ((gp = cp) == NULL) printf ("(GP = CP) is NULL\n"); while (1); }
#define TRUE 1 #define FALSE 0 void main() { char* p; char* q; char xdata* xp = NULL; bit fail = FALSE; // This fails. if ( p = xp ) { fail = TRUE; } // This is ok. if ( ( q = xp ) == NULL ) { fail = TRUE; } }
It's always interesting to discuss whether an implementation of a C compiler conforms to the standard or not. Here is my 2 cents. The ISO C 1999 says: An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function. ... Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal. Let's see if C51 conforms to that. Forgive me if I make mistakes here, since I only did C166. It's easy to satisfy the first requirement: a null pointer can't point to an object in C program. Just make sure that the linker doesn't put anything to where the null pointers point. The second requirement:
if ( (void*)0 != (void xdata*)((void*)0) ) printf("Oops! This is not ISO C behaviour.");
My point is that it's probably OK to break standard conformance in some places if it's justified by performance/simplicity/etc considerations. After all, if one wants standard C behaviour, they can limit themselves to using generic pointers only. I guess that this is the nub of the matter. If you write standard C code, you will always be getting generic pointers and, hence, standard C behaviour. Memory specific keywords are Keil extensions and if you choose to use them it is necessary to understand how they work in some detail.
Now the question is which one of these is a pointer to offset 0 (in a particular memory space) and which is NULL? From the point of view of the C standard, I think it's pretty clear: they're all NULL, and should compare equal to any null pointer. Based on the definition of NULL (which is defined as ((void *) 0) ) the only candidate for NULL is a generic pointer to idata address 0x00. You're thinking of (generic) pointers as integers, it seems. But they aren't numbers. They're abstract thingies that behave somewhat, but not quite like numbers. Actually, the C standard does not define NULL as (void *)0, either. #define NULL 0 would be every bit as valid. My point would be that in a comparison of the type
(generic pointer == 0)
(generic pointer == NULL)
(void *)(xdata char *)0 == 0
Jon, Thanks so much for this posting. It really hit home regarding the NULL value problem even after I had read the section on generic pointers and conversion. The issue to me is that during the comparison I am looking at the memory-type, which I do not care about. Here is a suggestion that might work.
char * var; if ((unsigned short) var == (unsigned short) NULL) {}
This will strip out the memory types on both sides and then a valid comparison can be made. What do you think? I'd probably do something like this:
#define ISNULL(x) (((unsigned) (void *) (x)) == 0) void * p; // gerneric pointer . . . if (ISNULL(p)) { // NULL pointer detected }
It would be worth you taking a look here: http://www.keil.com/update/_docs/releasenotes/c51v709.htm as there have been a number of bug fixes throughout version 7.x releases relating to comparisons between generic/far pointers and NULL. Stefan