7 ms·
The cited incorrect assumption was "null pointer dereferences are guaranteed to crash the program", which is, as you implied, not true because it won't necessar
by robertelder 10y ago
The 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.