That'd be a completely standards-compliant C implementation.
Your code wouldn't run on it. Your code would not be portable to my standards-complaint implementation. Therefore your code is not portable.
That'd be a completely standards-compliant C implementation.
Your code wouldn't run on it. Your code would not be portable to my standards-complaint implementation. Therefore your code is not portable.
What happens when you access memory which has not been successfully allocated?
Strictly less (not more than every 4KB), but, yes :)
> What happens when you access memory which has not been successfully allocated?
Portable C has finite size_t. Actual machines, virtual or otherwise, have finite resources. At some point you may run out of resources. With a dynamic stack allocation checker you'll fail with a segmentation fault when you run out. Without a dynamic stack allocation checker (but with dynamic stack allocations) you may well fail in random ways due to corruption of other stacks, heap, mmaps, etc.
If your C run-time doesn't have stacks then a dynamic stack allocation checker may be pointless... unless your OS is over-committing memory, then it may cause failure to happen sooner than it might otherwise.
Who says you will?
Nothing in the C specification says that.
Real life has no machines with 64-bit ISAs and 2^64 bytes' worth of resources.
Real life isn't portable. Who knew.
If there's no guard pages, no segmentation faults, no OOM killers, etc., then an allocation checker will cause failure in much the same kinds of undefined ways as not having the checker.
A segmentation fault is what you can expect in real systems, and it's absolutely better than undefined behavior.
You can do this portably, in C. It may not do you any good, and it may waste CPU cycles and memory bus usage. But it shouldn't harm. If the alternative is to not use alloca() or VLAs, and to make it so you can statically know your program's resource needs (which is very limiting, and not really attempted outside contexts where that such limitations are very strongly desired), then do that.
We all know what you're saying works in practice on most machines.
But it's not pure C and it's not portable if you follow the spec.
Also, in the absence of stacks, there's no "direction in which the stack grows", and any attempt to determine that direction will yield a nonsensical result, but doing memset() in one direction or the other will also then be equivalent anyways.
EDIT: There is one non-portable thing here: the assumption that machines are more limited than size_t implies. However, that's a distinction without a difference for any 64-bit ABIs or larger because all such machines must be more limited than size_t implies. That there is any failure mode when resources are exhausted is a non-portable assumption is uselessly true. Not being able to assume defined resource exhaustion failure modes would be destructive -- that the C spec does not is irrelevant.
This is undefined behavior.
See n1570.pdf, Annex J page 560, J.2 Undefined behavior
Pointers that do not point to the same aggregate or union (nor just beyond the same array object) are compared using relational operators (6.5.8).
So such a simplistic stack growth detector may get the wrong answer, but it's not undefined behavior:
char stack_grows_up(uintptr_t callers_local) {
char my_local = (uintptr_t)&my_local > callers_local;
return my_local;
}
EDIT: Ah, and uintptr_t is an optional type. If the platform doesn't have it (e.g., because it's a segmented memory architecture) then this won't work, so it's not portable, but aside from that it's not undefined behavior, it's just that the computation of stack growth direction may well be wrong.Nope. Just today I've seen a stack corruption bug that instead of producing a segmentation fault caused the process to go into uninterruptible sleep, somehow.
It seems to me like it is impossible to do poking in a way that could not be elided by the compiler, because removing poking never alters the semantics of the program (stack overflow or guard pages are not part of the semantics of C, I believe).
Of course, in practice you'll probably get the right behavior, but I'm personally always interested in language lawyery questions. I do believe that there are cases of security issues introduced by compilers eliding memset operations on password buffers, because the compiler can tell that the result of memset is never used and so it doesn't actually clear the memory. These sorts of things are tricky to get right.
You can cast to volatile, but casting away volatile-ness and dereferencing the resulting pointer is UB.
If you need that part, and that part is not portable, then your solution is not portable is it!