4 ms·
I think you do have a point about possibly misleading people in relation to signed overflow, since that is common hardware behaviour, but for the point about nu
by robertelder 10y ago
I think you do have a point about possibly misleading people in relation to signed overflow, since that is common hardware behaviour, but for the point about null pointer de-reference, I believe thinking in terms of assembly would actually help people realize that referencing memory location 0 (if that is what null was defined to), will not immediately crash your program. It is not special, it's just another memory location.
Your point about teaching assembly as being a waste of time also has some merit. Of course people still do program in assembly, but it is only becoming more and more rare to actually need someone to program assembly, which is why it just isn't emphasized as much any more and it makes C seem like an even stranger language for someone who started on Python or Javascript.
- pcwalton 10y ago> I believe thinking in terms of assembly would actually help people realize that referencing memory location 0 (if that is what null was defined to), will not immediately crash your program. It is not special, it's just another memory location. No, it is special. Dereferencing it is undefined behavior. It is not guaranteed to result in a load at address zero.
- robertelder 10y agoThe cited incorrect assumption was "null pointer dereferences are guaranteed to crash the program", which is, as you implied, not true because it won't necessarily crash the program. C places additional restrictions which say that the program can actually do anything (undefined behaviour). 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.
- scott_s 10y ago> C cannot do anything that assembly language cannot do. 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.
- robertelder 10y agoMy name is Robert Elder and I approve this message.
- RodgerTheGreat 10y agoWith modern compilers and modern CPUs, C isn't a particularly thin abstraction. Many serious misconceptions stem from naive assumptions about how C will be translated into assembly which are invalidated through compiler code transformations- often with results that surprise even expert compiler maintainers. Please do not spread the idea that C has any straightforward or predictable relationship with assembly language.
- z92 10y ago"more straightforward and more predictable relationship with assembly language than any other language in the world" is the right way to say it.
- RodgerTheGreat 10y agoThat's a rather bold assertion. How would you back it up? What about high-level assembly languages, ISA-agnostic IRs or languages like Forth or Oberon for which compilers intentionally perform only simple optimizations, if any?
- maxlybbert 10y ago> The main reason I keep coming back to assembly language with C, is that C cannot do anything that assembly language cannot do. 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-know.html http://blog.llvm.org/2011/05/what-every-c-programmer-should-... , http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html http://blog.llvm.org/2011/05/what-every-c-programmer-should-... , http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html 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/ 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.
- robotresearcher 10y agoHas anyone ever seen any behaviour other than a segfault in the wild, though? Would any real-world compiler author decide that some other behaviour was reasonable? I can't imagine making that decision myself.
- sdk77 10y agoOn some microcontrollers I know a pointer to address 0 really is just that. I tested compiling the following 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.
- Kristine1975 10y agoOn MacOS Classic (no memory protection) you can happily write to address zero and replace whatever is stored there (I think Apple wisely left that memory address unused, though).
- pm215 10y agoOn BSD on the VAX, address 0 used to happen to hold 0, so dereferencing NULL got you zero. That was at the time a sufficiently popular platform that some programs assumed the behaviour (eg, that a null pointer could be used as an empty string), and you can still find older docs on the web exhorting the reader to avoid "all the world's a VAX" syndrome.
- TorKlingberg 10y agoA classic problem is code like this: 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.
- krylon 10y agoI think on DOS in real mode address 0 was not special. Some(?) compilers would add code to programs to check that location on program exit to see if its value had changed and emit an error. (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).)
- jhomedall 10y agoBecause dereferencing a null pointer is undefined behavior, the compiler is free to assume it won't ever happen. This, in turn, can lead to the compiler optimizing or reordering code in a counter-intuitive manner, completely changing the behavior of a program that you thought you understood. The compiler might emit machine code that attempts to read the memory at location 0. It would also be perfectly within its rights to optimize away that branch of code (if it can prove that it always dereferences null). The code might even appear to work now, and completely break in the next version of the compiler.
- animeweedlord 10y agoThe page containing address zero (NULL) is almost always unmapped in environments with virtual memory, specifically to catch NULL pointer dereferences. That's not the source of the problem. The problem is that dereferencing a NULL pointer is "undefined behavior," and modern compiler writers abuse every instance of undefined behavior to implement negligible optimizations that subtly break programs for little gain.