I\'m writing C code for a system where address 0x0000 is valid and contains port I/O. Therefore, any possible bugs that access a NULL pointer will remain undetected and at t
Leave NULL alone and treat IO to port 0x0000 as a special case, maybe using a routine written in assembler, and thus not subject to standard C semantics. IOW, don't redefine NULL, redefine port 0x00000.
Note that if you're writing or modifying a C compiler, the work required to avoid dereferencing NULL (assuming that in your case the CPU isn't helping) is the same no matter how NULL is defined, so it's easier to leave NULL defined as zero, and make sure that zero can't ever be dereferenced from C.
The bit pattern for the null pointer may not be the same as the bit pattern for the integer 0. But the expansion of the NULL macro must be a null pointer constant, that is a constant integer of value 0 which may be casted to (void*).
To achieve the result you want while staying conformant, you'll have to modify (or perhaps configurate) your tool chain, but it is achievable.
You're asking for trouble. Redefining NULL
to a non-null value will break this code:
if (myPointer) { // myPointer is not null ... }
If you use the C std library, you'll run into problems with functions that can return NULL. For example the malloc documentation states:
If the function failed to allocate the requested block of memory, a null pointer is returned.
Because malloc and related functions are already compiled into binaries with a specific NULL value, if you redefine NULL, you won't be able to directly use the C std library unless you can rebuild your entire tool chain, including the C std libs.
Also because of the std library's use of NULL, if you redefine NULL before including std headers, you might overwrite a NULL define listed in the headers. Anything inlined would be inconsistent from the compiled objects.
I would instead define your own NULL, "MYPRODUCT_NULL", for your own uses and either avoid or translate from/to the C std library.
The C standard does not require null pointers to be at the machine's address zero. HOWEVER, casting a 0
constant to a pointer value must result in a NULL
pointer (§6.3.2.3/3), and evaluating the null pointer as a boolean must be false. This can be a bit awkward if you really do want a zero address, and NULL
is not the zero address.
Nevertheless, with (heavy) modifications to the compiler and standard library, it's not impossible to have NULL
be represented with an alternate bit pattern while still remaining strictly conformant to the standard library. It is not sufficient to simply change the definition of NULL
itself however, as then NULL
would evaluate to true.
Specifically, you would need to:
-1
.0
to check for the magic value instead (§6.5.9/6)There are some things you do not have to handle. For example:
int x = 0;
void *p = (void*)x;
After this, p
is NOT guaranteed to be a null pointer. Only constant assignments need be handled (this is a good approach for accessing true address zero). Likewise:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Also:
void *p = NULL;
int x = (int)p;
x
is not guaranteed to be 0
.
In short, this very condition was apparently considered by the C language committee, and considerations made for those who would choose an alternate representation for NULL. All you have to do now is make major changes to your compiler, and hey presto you're done :)
As a side note, it may be possible to implement these changes with a source code transformation stage before the compiler proper. That is, instead of the normal flow of preprocessor -> compiler -> assembler -> linker, you'd add a preprocessor -> NULL transformation -> compiler -> assembler -> linker. Then you could do transformations like:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
This would require a full C parser, as well as a type parser and analysis of typedefs and variable declarations to determine which identifiers correspond to pointers. However, by doing this you could avoid having to make changes to the code generation portions of the compiler proper. clang may be useful for implementing this - I understand it was designed with transformations like this in mind. You would still likely need to make changes to the standard library as well of course.
Considering the extreme difficulty in redefining NULL as mentioned by others, maybe its easier to redefine dereferencing for well-known hardware addresses. When creating an address, add 1 to every well-known address, so that your well-known IO port would be:
#define CREATE_HW_ADDR(x)(x+1)
#define DEREFERENCE_HW_ADDR(x)(*(x-1))
int* wellKnownIoPort = CREATE_HW_ADDR(0x00000000);
printf("IoPortIs" DEREFERENCE_HW_ADDR(wellKnownIoPort));
If the addresses you're concerned with are grouped together and you can feel safe that adding 1 to the address won't conflict with anything (which it shouldn't in most cases), you might be able to do this safely. And then you don't need to worry about rebuilding your tool chain/std lib and expressions in the form:
if (pointer)
{
...
}
still work
Crazy I know, but just thought I'd throw the idea out there :).