3 ms·
Compilers following the spec is necessary, but not sufficient. Compilers are expected to be useful, and in practice, that means being able to fail reliably when
by 10000truths 4y ago
Compilers following the spec is necessary, but not sufficient. Compilers are expected to be useful, and in practice, that means being able to fail reliably when spec violations occur. Let’s highlight what I personally expect, from my experience:
1. Pretty much every non-embedded CPU architecture has a page-based MMU.
2. Pretty much every compiler treats NULL as a zero value.
3. Every operating system using an MMU will leave the zero page unmapped, and load/stores at those addresses will cause a segfault.
4. If the pointer address cannot be inferred at compile time (e.g. via address of stack variables, or constant propagation), the compiler cannot optimize away the dereference.
Now WASM is a little bit different because it’s a virtual bytecode, but 99% of WASM builds use emscripten, and emscripten’s primary use case is porting C programs to the web with minimal changes, which means that in order to be useful, either emscripten (via instrumentation) or WASM (via memory protection feature) needs to be able to accommodate common target specific behaviors like memory protection.
- IX-103 4y agoWASM is a little more different than that, because 0 is a valid memory address -- the beginning of the heap. As you would expect this has all kinds of wonderful ramifications for code that assumes 0 is nullptr.
- 10000truths 4y agoYes - as mentioned in the article, 0 being a valid memory address is an unexpected gotcha. My point is that, even though the C spec allows for such an environment, it still causes lots of problems in practice, so it should be addressed.
- int_19h 4y agoIt should be noted that such an environment has been extremely common back when C was designed, and for a couple decades thereafter. This isn't something new that has just been sprung up unexpected on C coders.
- gpderetta 4y agoNote that the constant 0 being the null pointer is not an assumption but it is guaranteed by the standard. Whether that maps to address 0 it is an implementation detail, but any implementation that doesn't do that is looking for trouble.
- Kranar 4y ago>If the pointer address cannot be inferred at compile time (e.g. via address of stack variables, or constant propagation), the compiler cannot optimize away the dereference. It can't optimize away the dereference, but it can optimize away any NULL checks, so for example the compiler can transform this: if(y != NULL) { printf("Hello"); } int x = *y; Into this: printf("Hello"); int x = *y; It's possible that you never expect "Hello" to be printed if y is NULL, since there is a clear check for NULL before the printf, and yet... because of undefined behavior and compiler optimizations, the compiler is allowed to make assumptions about runtime behavior even if that behavior precedes an undefined operation. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633 https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...
- 10000truths 4y agoIn your example, even though the behavior is undefined according to the spec, the program will still reliably segfault as long as the target it runs on has the appropriate memory protections for the zero page. The crash will occur immediately after the elided null check, so the failure is localized to the problematic area and a debugger will show the problem.
- int_19h 4y agoIt won't occur immediately after the elided check, that's exactly the problem. It will allow the I/O to occur first. Now imagine if that was a database update.
- repsilat 4y agoMaybe off-topic, but here's an example of a null dereference that usually won't segfault because it never actually loads the address: struct X { int foo() { return 1; } }; X *x = nullptr; cout << x->foo() << endl;
- Kranar 4y agoFirst your statement is unfortunately just untrue, the idea that any behavior can be relied upon when performing an undefined operation is why to this day we have very buggy software written in C and C++ that lead to security vulnerabilities. There is nothing reliable about undefined behavior. Second, even if we accept for the sake of argument that your statement is true, the entire point of my example is that by the time you get to the dereference which produces a segfault, it's already too late. Any operation prior to that segfault that assumed a non-null pointer (such as the print statement) will have already been executed and produced potential side-effects. There is nothing local about this, it's simply that for the sake of an example I wrote a small code snippet, but in the article I linked to there are examples of very non-local bugs both spatially and temporally that would not be something you could reliably track down in a debugger.
- apaprocki 4y agoRe: 3. AIX (that you could buy today from IBM using the latest POWER chips) maps the zero page as read-only, so dereferencing NULL will not segfault.