No, it is special. Dereferencing it is undefined behavior. It is not guaranteed to result in a load at address zero.
No, it is special. Dereferencing it is undefined behavior. It is not guaranteed to result in a load at address zero.
The main reason I keep coming back to assembly language with C, is that C cannot do anything that assembly language cannot do. It only places additional restrictions on assembly, which is a bit easier to grasp (bitwise operations, add, subtract etc.). Once you understand the fundamental operations of the processor, you can start to learn the copious corner cases that is the C programming language.
I know where you're going with this, but I don't agree with this statement. C provides the abstraction of structs and unions, which do not exist in assembly. Arrays are also an abstraction which does not exist in assembly - yes, assembly provides the mechanisms to easily compute addresses for arrays, but that is different than having a named entity for many objects. The direction you're going is that C is a thin abstraction over assembly, which I agree with. But that is different than saying C has a one-to-one mapping, which is what I think your statement implies.
That's basically true. Except that an optimizer is allowed to transform things as long as the observable behavior remains the same. And undefined behavior means more than "just that one line is undefined" ( http://blog.llvm.org/2011/05/what-every-c-programmer-should-... , http://blog.llvm.org/2011/05/what-every-c-programmer-should-... , http://blog.llvm.org/2011/05/what-every-c-programmer-should-... ). These two facts conspire to make undefined behavior surprising to many programmers.
For instance -- using one of Lattner's examples -- on some architectures it's a little expensive to check if a loop variable had wrapped around. So the optimizer will omit the check for wraparound if it can prove wraparound is impossible. That is, if it can prove that n + 1 > n. But the Standard only requires wraparound for unsigned integer types. So the optimizer can also omit the check if it can only prove either n + 1 > n or n is a signed integer. In this case, a bounded loop turns into an infinite loop, but "undefined behavior" includes that kind of transformation.
More to the point, Linux had a severe security bug where they dereferenced a pointer, then checked if the pointer was NULL before returning the value ( https://lwn.net/Articles/342330/ ). The optimizer removed the check for NULL because if the pointer was valid when it was dereferenced, the check was unnecessary; and if the pointer was NULL when it was dereferenced, then dereferencing it was undefined behavior and "remove an NULL check" is a valid transformation in undefined behavior. Then moving that load to a different point in the function is also a valid transformation (as long as it doesn't affect observable behavior), and a few more transformations could make the NULL dereference do something completely different from what you expect.
unsigned short *a = 0;
((void (*)(void))a)();
which may cause a jump to address 0 (usually the reset vector).On R8C with the NC30 compiler there are no warnings and the output is:
MOV.W:Q #0H, -2H[FB]
MOV.W:G -2H[FB], R0
MOV.W:Q #0H, R2
JSRI.A R2R0
It jumps to 0. But on PIC 16 with the XC compiler a warning is emitted warning: (1471) indirect function call via a NULL pointer ignored and the statement is not compiled at all. printf("a=%p *a=%d", a, *a);
if (a == NULL) return;
An optimizing compiler will likely remove the NULL check completely because the printf above it has undefined behavior if a is NULL.(I think I read about this somewhere, but it was a while ago, and I never really programmed for DOS (well, one little program to set the serial port to some specific settings, but that was about ten lines including whitespace and a little comment explaining what the code did).)