- Has "stack overflow" specified behavior? - 13 Updates
| Paavo Helde <eesnimi@osa.pri.ee>: Dec 13 01:51AM +0200 12.12.2021 10:42 wij kirjutas: > } > --- > Has "stack overflow" specified behavior? No. Stack overflow is arguably the least specified behavior of them all. The stack size is extremely limited (few MB), compared to the RAM amounts current computers have (tens of GB). There is no standard-defined way to detect stack overflow, not to speak about handling it. That's one reason why using stack-allocated things like std::array needs special care, especially when writing libraries (which need to execute in a stack of unknown size and fill-up). There are some implementation-defined ways though to survive stack overflows, but it's not so easy. You cannot continue the program if there is no more stack space, so the only way is to throw an exception. Alas, there is no "throw" statement in the code, so this would be an "asynchronous" exception appearing at a pretty random place in the code, meaning that the compiler must cope with such exceptions, which may easily slow down the whole program (witness the /EHa compiler option in MSVC). BTW, your example code is not guaranteed to cause stack overflow, it might go into an infinite loop instead because of tail recursion, or become a zero op by optimizing the whole t() function away, either as UB or as a code with no effect. |
| Juha Nieminen <nospam@thanks.invalid>: Dec 13 05:48AM > amounts current computers have (tens of GB). There is no > standard-defined way to detect stack overflow, not to speak about > handling it. That made me think: Why has neither the C nor the C++ standardization committees ever thought of adding a standard library utility to get the current amount of free stack space? Sure, perhaps in some operating systems this isn't something that programs can get, but the function in question could be optional in that sense. For example it could return -1 to indicate "this operation is not supported", else a non-negative value to indicate the amount of free stack space. This could give programs at least the opportunity to gracefully do something if running out of stack space. I think this could be useful especially in programs that need to be as stable and secure as possible. (In fact, getting the amount of free (physical) RAM available to the process could also be useful, for similar reasons. It could behave in the same way: -1 if the operation is for some reason not supported, else the amount of free RAM (not counting swap).) |
| Bo Persson <bo@bo-persson.se>: Dec 13 10:44AM +0100 On 2021-12-13 at 06:48, Juha Nieminen wrote: > process could also be useful, for similar reasons. It could behave > in the same way: -1 if the operation is for some reason not supported, > else the amount of free RAM (not counting swap).) This would be of very limited use. The result of a call from one thread wouldn't be valid long, if the other threads allocate and free memory already while the result is returned. It is similar to filesystem::exists("path"). If you get a true or false back, how long is the result valid? Nanoseconds? |
| wij <wyniijj@gmail.com>: Dec 13 04:44AM -0800 On Monday, 13 December 2021 at 17:44:58 UTC+8, Bo Persson wrote: > already while the result is returned. > It is similar to filesystem::exists("path"). If you get a true or false > back, how long is the result valid? Nanoseconds? If such a function is useful, the duration the value valid is not a problem. The only useful cases I have are when implementing 'synchronized' thread (not sure about the name) or the function algorithm uses stack heavily, e.g. when the size of an object is significantly large. |
| Richard Damon <Richard@Damon-Family.org>: Dec 13 07:47AM -0500 On 12/13/21 4:44 AM, Bo Persson wrote: > already while the result is returned. > It is similar to filesystem::exists("path"). If you get a true or false > back, how long is the result valid? Nanoseconds? Actually, most threading libraries creat threads with a FIXED sized stack (specified in the create call or it uses a default), and that space is all reserved for the thread stack. Only the main thread tends to have an expandable stack, which often grows into the heap, so only the main thread would be affected by other threads activities. I suspect that part of the issue is that while it would normally be possible to compute how much address space is available for the stack, it can be more complicated to figure out if you can map usable ram into that address space, and with the Linux over-commit issue, the straight answer might easily be not available, or expensive to compute. Also, this is something easy for a system to add as a system defined function, that works for it. The fact that this isn't commonly available seems to be a sign that it isn't really easy to provide what might be needed, or it isn't really needed. |
| Manfred <noname@add.invalid>: Dec 13 05:08PM +0100 On 12/12/2021 9:42 AM, wij wrote: > } > --- > Has "stack overflow" specified behavior? Putting apart the specific example, the standard describes the behavior of the abstract machine only, but Appendix B refers to constraints posed by actual implementations, and that includes the "nesting levels of compound statements" (which in turn include function bodies). So, what you call stack overflow (an expression not found in the standard) is in fact a possible violation of a constraint posed by the implementation. As a kind of constraint violation this leads to UB - specifically I'd consider this under n4860 p4.1 clause (2.3) "If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program". With respect to the example given, n4860 p6.9.2.2 gives explicit permission to an implementation to remove the loop, and compile the whole program as a no-op (ref. "observable behavior"). |
| Manfred <noname@add.invalid>: Dec 13 05:23PM +0100 On 12/13/2021 6:48 AM, Juha Nieminen wrote: > That made me think: Why has neither the C nor the C++ standardization > committees ever thought of adding a standard library utility to get > the current amount of free stack space? The more general answer is that the standard (both C and C++) describes the behavior of an "abstract machine", and the requirement for actual conformant implementations is to produce the same "observable behavior" as the abstract machine. The standard does not even mandate a stack [*]; it specifies the language rules for function calls, scope, local variables etc. all of which is commonly implemented via a memory stack, but that's the implementation, not the abstract machine. The standard then connects back to the real world in Appendix B, where it says that limitations of finite systems may result in program constraints that should be documented by the implementation. In this perspective such constraints fall under the "implementation defined" category. > process could also be useful, for similar reasons. It could behave > in the same way: -1 if the operation is for some reason not supported, > else the amount of free RAM (not counting swap).) [*] The standard talks a.o. about "stack unwinding", thus referring to the common concept of a stack, but only to describe the process of destructing objects with automatic storage when leaving their scope. So, it's a concept related with scoping and lifetime, not with the stack memory structure. |
| James Kuyper <jameskuyper@alumni.caltech.edu>: Dec 13 12:13PM -0500 On 12/13/21 4:44 AM, Bo Persson wrote: >> That made me think: Why has neither the C nor the C++ standardization >> committees ever thought of adding a standard library utility to get >> the current amount of free stack space? A key factor in that decision is the fact that neither the C nor the C++ standard ever talks about the stack space, a fact that allows either language to be implemented on systems where the concept of "stack" is meaningless. On operating systems where the concept is meaningful, there often is a way to conduct such a query. For instance, on Unix-like systems, there's getrlimit(RLIMIT_STACK, &rlim). ,,, > already while the result is returned. > It is similar to filesystem::exists("path"). If you get a true or false > back, how long is the result valid? Nanoseconds? There's an important difference: as a matter of the policies you use for managing a filesystem (rather than anything enforced by the operating system), it is not only possible, but commonplace, to be certain that a given file, if present, will remain in existence long enough to do whatever it is you want to do with it. That's not the case with the amount of free stack space. |
| "Öö Tiib" <ootiib@hot.ee>: Dec 13 12:05PM -0800 > standard ever talks about the stack space, a fact that allows either > language to be implemented on systems where the concept of "stack" is > meaningless. That feels like quite odd argument. How can that distract anyone from fact that every implementation has to have some kind of storage that is used for storing objects with automatic storage duration? Neither standard denies that it has to exist nor that it is potentially limited. Avoiding naming it with some shorter name does not make it disappear from abstract machine. It may run out of available space and the standards avoid providing any ways to estimate that space or to handle that event. |
| Tim Rentsch <tr.17687@z991.linuxsc.com>: Dec 13 01:13PM -0800 > } > --- > Has "stack overflow" specified behavior? The expression '++a' tries to read an uninitialized variable. After correcting for that oversight (for example, by giving a value to 'a' at its declaration by 'int a = 0;'), the program has defined behavior. To be more specific, each of the operations asked for in the program has a well-defined description of what is to happen in the abstract machine, which means the program as a whole has defined behavior. Note that this conclusion is about what will take place in the /abstract/ machine, and not about what occurs if and when the program is run in an /actual/ machine. The C++ standard explicitly lets executing a program in an actual machine off the hook for running out of any kind of limited resource, including but not limited to "stack space". Section 4.1 paragraph 2.1 of n4860 says this: If a program contains no violations of the rules in this document, a conforming implementation shall, within its resource limits, accept and correctly execute that program. So even though the program has defined behavior, it may very well fail due to running out of stack space when executed. Moreover that applies to all programs, for any kind of resource the implementation might depend on. Short summary: the program (not counting the uninitialized access) has defined behavior, but may fail because of stack overflow during an actual execution. |
| Tim Rentsch <tr.17687@z991.linuxsc.com>: Dec 13 01:50PM -0800 > violation of a rule for which no diagnostic is required, this document > places no requirement on implementations with respect to that > program". First, I think you mean Annex B, not Appendix B. Second, Annex B never uses the word 'constraint'. Third, Annex B is informative, not normative. Nothing it says can change the rules governing the C++ language. (Side note: Annex B itself says in the last sentence of paragraph 2: However, these quantities are only guidelines and do not determine compliance. End side note.) The program shown above (after fixing the problem of reading an uninitialized variable) has defined behavior, not undefined behavior. An execution of the program in an actual machine may fail due to running out of stack space (or any other resource) per section 4.1 paragraph 2.1. Despite that, what happens in the abstract machine is well-defined, and so the program has only defined behavior, and no undefined behavior. |
| Tim Rentsch <tr.17687@z991.linuxsc.com>: Dec 13 02:16PM -0800 > might go into an infinite loop instead because of tail recursion, or > become a zero op by optimizing the whole t() function away, either > as UB or as a code with no effect. It's important to distinguish the two realms of abstract machine and actual machine. In the abstract machine, the program shown above (after fixing the problem of reading an uninitialized variable) does have a well-defined specification, and the program as a whole has defined behavior. Whether a program has defined behavior or undefined behavior is determined solely by what goes on in the abstract machine (which may depend on values read from a file or other input device, etc, but still the question is to be answered considering only what happens in the abstract machine, with reference to any actual machine). Everything the program does has a well-defined specification, and so the program has only defined behavior, and no undefined behavior. In an actual machine, an implementation is obliged to carry out the abstract semantics only to the extent that the execution does not exceed the implementation's "resource limits", which might be anything at all, including stack space. Once such a resource limit is exceeded, the implementation has no further obligations, and may abort, or whatever. But that isn't the same as undefined behavior, which depends solely on what the standard says about operations in the abstract machine. |
| wij <wyniijj@gmail.com>: Dec 13 03:07PM -0800 On Tuesday, 14 December 2021 at 06:16:23 UTC+8, Tim Rentsch wrote: > obligations, and may abort, or whatever. But that isn't the > same as undefined behavior, which depends solely on what the > standard says about operations in the abstract machine. If the concept of abstract (ideal) machine is used (the 1st time I heard this term in use). The infinite recursive call should be defined as it is (never return, or infinite loop except semantics 'optimized' to differ), all functions within should be carried out successfully. But, for this ideal to be anything reasonable, there should at least one machine that can execute the program correctly. If this is accepted, what should this 'actual machine' do with the infinite recursive call? |
| You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment