3 ms·
What I'm describing is portable. It may be unnecessary in C run-times that allocate call frames on the heap, but those aren't realistic C run-times.
by cryptonector 5y ago
What I'm describing is portable. It may be unnecessary in C run-times that allocate call frames on the heap, but those aren't realistic C run-times.
- chrisseaton 5y agoSay I ran your code on a special C implementation that tracks all memory access and disallows it when you try to read from stack memory that doesn't contain a live object. 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.
- cryptonector 5y agoYou can almost certainly make it portable. After all, the allocation has a pointer, size, and type, and if it is at all an array, you could dereference every Nth element as needed, possibly with memcpy(), and avoid aliasing rules via casting to unsigned char *.
- chrisseaton 5y agoSo you're going to touch every byte of the memory, to check it's been successfully allocated. What happens when you access memory which has not been successfully allocated?
- cryptonector 5y ago> So you're going to touch every byte of the memory, to check it's 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.
- chrisseaton 5y ago> you'll fail with a segmentation fault Who says you will? Nothing in the C specification says that.
- cryptonector 5y agoThe C specs assume unlimited (up to size_t) resources. 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.
- Joker_vD 5y ago> A segmentation fault is what you can expect in real systems, 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.
- chrisseaton 5y agoYou're saying 'if run on a system that does things in a way I expect then it works' but the point of the article is 'but what about if you want to run on a system that's not as you expect and you want to run pure C'. 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.
- cryptonector 5y agoIt's portably assuming that you can cast an allocation's pointer to a pointer to unsigned char. It's like assuming you can memset(), since that's effectively what it's doing. In the absence of memory over-commit, this will have no effect beyond wasting resources. In the presence of over-commit -bounded stacks resemble memory over-commitment!- it will have the effect of committing the memory OR failing in whatever way the machine fails when resources are exhausted. However, in the latter case, and when the machine uses stacks for C function calls, the failure will be defined by the machine rather than undefined (due to memory corruption). 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.
- Hercuros 5y agoWouldn't the compiler be allowed to elide the memory accesses that are being used to poke the memory region? If you are doing the poking before the memory has been initialized, then I'm not sure there are any guarantees that the compiler will read from the actual memory region (since it's uninitialized). And if you do writes to do the poking then there might be similar issues if the result of the write is never used. Even for reads you would have to make sure that the result gets used. Also not sure if casting to volatile is allowed if the object itself is not volatile. This is all true regardless of UB. 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.
- cryptonector 5y agoYes, you need to find a way to have the compiler not elide memset() or this particular function. That part is probably not portable. You can cast to volatile, but casting away volatile-ness and dereferencing the resulting pointer is UB.
- chrisseaton 5y ago> That part is probably not portable. If you need that part, and that part is not portable, then your solution is not portable is it!
- kevin_thibedeau 5y agoThis wouldn't work for exotic architectures like a Symbolics Lisp machine where pointers aren't numeric indices into memory. C has been kept loosey-goosey about platform requirements to avoid ruling out future architectural possibilities that haven't been anticipated yet. You can't assume anything about memory organization and still be truly portable.