It is not reasonable for isspace(), a simple function call with no pointer arguments, to fault like that, absent unlikely events such as a hardware issue or a bad stack pointer.
Well, if it internally uses a lookup table and doesn't do bounds checking, then it can fail this way, and that failure will propagate to the caller.
> You are describing a failure mode for a bad pointer dereference, which generally should not be handled in-process and should take down the program.
Maybe this is where we went wrong? Bad pointer access isn't fundamentally different from doing a bounds check and throwing an exception. Either of them will tank the process if not handled up the stack. But when you do a bounds check solely to prevent a bad pointer access crash, you're effectively re-doing the work the OS already does for you, paying a cost at runtime just to switch to a slightly different semantics. Maybe instead of doing that, we should have the platform provide better granularity of protection (as to e.g. avoid bad pointer access targeted to overwrite some other memory), and lean on it, instead of bolting extra layers of the same thing, just Invented Here on top?
> Well, if it internally uses a lookup table and doesn't do bounds checking, then it can fail this way, and that failure will propagate to the caller.
We're talking UB here, AFAICT. So it might do literally anything, including propagating to the caller. Or not. Or doing something entirely different, like returning true (or false)... or deleting your files.
One wishes that out-of-bounds access would guarantee a SIGSEGV, but here we are...
> Bad pointer access isn't fundamentally different from doing a bounds check and throwing an exception.
Performance is the difference. If you're doing bounds checks for every access you're going to be slower than a compiler which can prove (for a loop, say) that no OOB access can possibly occur during said loop and just remove the bounds checks.
> Either of them will tank the process if not handled up the stack.
Again, not true -- an exception has defined behavior, a stray pointer doesn't. (It could, conceivably, but it fundamentally doesn't in C.)
No. I think your mind must have its grooves set by high level languages to think this way. I was very deliberate when I said it's unwise to recover from bad pointer dereference in your own process. It is not safe and invites a world of pain. It isn't "an exception" that you can "catch and move on from". It is death. Yes you can install a handler for SIGSEGV or Win32 access violation. That doesn't make it a good idea.
You can handle it safely, but only from the safety of another process. Or the kernel can handle it in a page fault interrupt for a user process. This is because your own address space is known good in those cases. But handling it in your own address space, I would not advise that.
Re: Your use case, I believe the most recent C++ standard has a portable way to do stack traces, so yay!
The OS doesn't know where your array end, so the error would have to depend on whether the access is outside the memory allocated to the process. A function specification of "if the input is outside valid range, either return garbage or raise an exception" provides no value over "either return garbage or crash the program", because there is still the possibility of returning garbage.
Right. Yes, I was being a little tongue-in-cheek with the original comment, but only a little - imagine if there was a way for the OS / underlying runtime to prevent returning garbage (possibly by eagerly raising an exception). In that case, a lot of our error handling would be duplicating the work done by the OS. Now, with the garbage result being a possibility, we're still duplicating some of the work - we just can't really avoid it, in a kind of "50% of checks are redundant, we just don't know which ones" way.
I'm raising this as something to think about, that wasn't obvious to me until recently. I grew up dreading SIGSEGV and 0xC0000005. But recently, having no other choice but to trap some of those and similar exceptions (due to bugs in some proprietary third-party dependencies), I finally realized those are just error handling mechanisms, conceptually not any different from regular exceptions or Result<T, E> types. They're just implemented one layer below.
SIGSEGV is a safety/isolation feature. It is a byproduct of physics limitations: if we had infinite RAM, we would allocate a separate 2^64 address space to each process and SIGSEGV would not exist. There are also zero guarantees that you will ever get a SIGSEGV, even if you do horrible, no-good, very bad things: that is entirely dependent on the OS and hardware. This allows for efficient implementations because the OS/hardware can implement checks at whatever granularity they deem appropriate. On some OS/hardware combinations you may never get a SIGSEGV at all!
On the other hand, ensuring errors on, amongst others, out-of-bounds array accesses, is much more expensive to do dynamically, and the OS can't really do it more efficiently than the compiler. In fact, it is the opposite: the compiler knows the specifics of the language and can exploit them to elide many boundary checks that the OS never could.
True, but I would say that wouldn't count as a reasonable implementation.
…
if (foo.booleanValue()) { }
Can also NPE.
null, None, Some(TRUE) and Some(FALSE)
Of course, if you want more than that, you can have unboundedly many by invoking new Boolean(), which guarantees its return value is unique according to ==