Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
279 views
in Technique[技术] by (71.8m points)

c - Redefining NULL

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 the same time cause dangerous behaviour.

For this reason I wish to redefine NULL to be another address, to for example an address that isn't valid. If I accidentally access such an address I will get a hardware interrupt where I can handle the error. I happen to have access to stddef.h for this compiler, so I can actually alter the standard header and redefine NULL.

My question is: will this conflict with the C standard? As far as I can tell from 7.17 in the standard, the macro is implementation-defined. Is there anything elsewhere in the standard stating that NULL must be 0?

Another issue is that plenty of compilers perform static initialization by setting everything to zero, no matter the data type. Even though the standard says that the compiler should set integers to zero and pointers to NULL. If I would redefine NULL for my compiler, then I know that such static initialization will fail. Could I regard that as incorrect compiler behaviour even though I boldly altered compiler headers manually? Because I know for certain that this particular compiler does not access the NULL macro when doing static initialization.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

  • Arrange for literal zeros in assignments to pointers (or casts to pointers) to be converted into some other magic value such as -1.
  • Arrange for equality tests between pointers and a constant integer 0 to check for the magic value instead (§6.5.9/6)
  • Arrange for all contexts in which a pointer type is evaluated as a boolean to check for equality to the magic value instead of checking for zero. This follows from the equality testing semantics, but the compiler may implement it differently internally. See §6.5.13/3, §6.5.14/3, §6.5.15/4, §6.5.3.3/5, §6.8.4.1/2, §6.8.5/4
  • As caf pointed out, update the semantics for initialization of static objects (§6.7.8/10) and partial compound initializers (§6.7.8/21) to reflect the new null pointer representation.
  • Create an alternate way to access true address zero.

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...