[HN Gopher] My favourite C++ footgun ___________________________________________________________________ My favourite C++ footgun Author : pabs3 Score : 99 points Date : 2021-06-21 09:29 UTC (1 days ago) (HTM) web link (dustri.org) (TXT) w3m dump (dustri.org) | ot wrote: | In modern C++ the use of `new` and `delete` should be considered | smell, for this and other reasons. | fooker wrote: | That's not a valid evaluation order, afaik. You can not start | evaluating one argument, stop and switch to the other, then come | back to the earlier one. | MaxBarraclough wrote: | Do take a look at dvt's comment. Prior to C++17, it was a valid | evaluation order. | bregma wrote: | Prior to C++17 such an evaluation order would have violated | the standard. ISO/IEC 14882:2011 1.9/13 [Note: | Indeterminately sequenced evaluations cannot overlap, but | either could be executed first. -- end note]. The arguments | in a faction call are a comma-separated indeterminate | sequence (5.2.2/4 [Note: Such initializations are | indeterminately sequenced with respect to each other (1.9) -- | end note]). | MaxBarraclough wrote: | > Prior to C++17 such an evaluation order would have | violated the standard | | Herb Sutter disagrees. [0] The C++ legalese you've quoted | seems decisive, unfortunately Sutter's post doesn't mention | it. | | See also [1][2]. | | _edit_ quietbritishjim beat me to it, and with a better | informed comment to boot. | | [0] https://herbsutter.com/2013/05/29/gotw-89-solution- | smart-poi... (ctrl-f for _known_ , and see also the comment | thread at the bottom of the page) | | [1] https://stackoverflow.com/a/48844115/ | | [2] https://stackoverflow.com/a/46472497/ | ectopod wrote: | Is there any version of C++ in which this interleaving is | permitted? | quietbritishjim wrote: | The full context to that second quote is: | | > When a function is called, each parameter ([dcl.fct]) | shall be initialized ([dcl.init], [class.copy], | [class.ctor]) with its corresponding argument. [ Note: Such | initializations are indeterminately sequenced with respect | to each other ([intro.execution]) -- end note ] | | So it's only about initialisation of parameters from | arguments, not to the evaluation of arguments. (For example | if foo() takes a std::string parameter, and bar() returns a | const char*, then in the expression foo(bar()) the above | quote refers to the call to the string constructor, _not_ | to the call to bar()). | | The more relevant part is 5.2.2/8: | | > [ Note: The evaluations of the postfix expression and of | the argument expressions are all unsequenced relative to | one another. All side effects of argument expression | evaluations are sequenced before the function is entered | (see [intro.execution]). -- end note ] | | As others have said, this didn't change until C++17. | eklitzke wrote: | The author writes "this might be why there is std::make_shared", | but that's not really why it exists. When you create a | std::shared_ptr it needs to allocate memory for a control block | structure (which tracks the reference count) as well as the | memory for the object itself. If you write | std::shared_ptr<Foo>(new Foo) this will result in two memory | allocations, one for the Foo object and the other for the | std::shared_ptr control block. If you write | std::make_shared<Foo>() then a single allocation will happen | which has enough space for both the control block and the Foo | object, and then placement new is used to initialize the memory | for both structures. So std::make_shared exists to reduce the | number of memory allocations that are being made, not for the | reason suggested here. | quietbritishjim wrote: | It exists for both reasons. | | That's why there's also a std::make_unique even though it | doesn't have a control block (although it was added later, but | that was just an oversight). | MaxBarraclough wrote: | > It exists for both reasons. | | I believe this is correct. Here's [0] some old Boost | documentation on Boost's _make_shared_ which inspired the C++ | standard 's _make_shared_. It mentions both reasons. | | [0] https://www.boost.org/doc/libs/1_67_0/libs/smart_ptr/doc/ | htm... | haldean wrote: | There's a crazy downside to make_shared that I learned recently | because of this: if you have a weak pointer to a shared thing, | and the refcount for the shared thing drops to zero, the weak | pointers will keep the allocation for the object "alive", | because they still need access to the remnant and the remnant | was created in the same allocation as the object so they can't | be freed separately. So now I only use make_shared if I know | for sure there won't be a weak_ptr pointing at it (or if the | base object has a relatively small memory footprint after it's | been destructed). | ot wrote: | Yeah, that's definitely something to be aware of. It's | usually not an issue as most objects have small footprint | (and any allocations they in turn hold would be released when | the _strong_ refcount goes to 0). | asveikau wrote: | I think the idea that you are also relying on a presumably well | tested library to get the exception corner cases right is also | noteworthy. Memory leaks on allocation failure are pretty | common in naive code, and a good thing to handle in a library | where it can get well thought out. | Koshkin wrote: | This is very important. Try to hide most of complexity in a | library, and then unit-test the hell out of it. | CraigJPerry wrote: | A capturing lambda is another way to leak a std::shared_ptr<> - i | feel like it's an even easier way to run into this kind of | footgun but i suppose it depends whether you typically use lots | of lambdas. | | A reasonable solution in my case could be to use std::weak_ptr<> | instead but that wouldn't be useful here. | quietbritishjim wrote: | > A capturing lambda is another way to leak a std::shared_ptr<> | | How so? Do you mean if you do exactly what the article says | when initialising the capture variables? Or something lambda | specific? | overgard wrote: | My (least) favorite footgun is "auto" when it comes to | references. | | If you want to get a pointer from something, you would write | this: auto fooPtr = mywidget.getPtr(); | fooPtr->doCoolStuff(); | | But we all know references are better than pointers right? So we | should just write... auto fooRef = | myWidget.getRef(); fooRef.doCoolStuff() | | The problem is... this is valid and will probably not do what you | want. It will make a copy. What you want is actually | auto& fooRef = myWidget.getRef(); | | I once spent an entire day debugging a bizarre crash because of | that. I was asking for a reference to a scene graph, and what was | actually happening is I was getting a clone of the scene graph | (which was an object that couldn't be safely copied), and then | destroying a bunch of shared pointers when the function returned. | Fun times. | criddell wrote: | If you develop on Windows and aren't using Visual Studio 2019. | | "auto fooRef = myWidget.getRef()" will get a little squiggle | under it to warn you that you are making a copy. | overgard wrote: | I am very happy tooling is helping to make these sorts of | mixups easier to find, although sadly C++ is such a hard | language to write tooling for that those kinds of nice | features are pretty rare. I remember back when I had to work | in Xcode a few years ago I could rarely even get basic | intellisense to work. | Koshkin wrote: | > _and aren 't using Visual Studio_ | | OK, I tried this in Notepad, and it did not work. | malkia wrote: | yeah, same with WordPad, but see Word would give you some | squiggles :) | amluto wrote: | > What you want is actually > > auto& fooRef = | myWidget.getRef(); | | That variant has issues if getRef() actually returns a | temporary object. You may want auto&&. | bregma wrote: | Wait, you had an object that was unsafe to copy, but you told | the computer it was safe by not deleting the copy constructor? | I guess it should have done what you wanted, not what you told | it. | contravariant wrote: | Yeah I learned that the hard way when I found out that | std::vector _apparently_ feels free to move your objects | around in memory for you, no matter if you point to them from | somewhere else. | | I mean in hindsight it's obvious, but still not exactly what | I wanted to happen. | | For reference, what I did does actually work, temporarily. | daniel-levin wrote: | I think if you come from the JVM / CLR world, you are so | protected by the runtime's patching up of references that | it might not even occur to you that a (raw) pointer to a | data structure's internals can dangle after the data is | moved around. The runtimes mentioned pause your code, move | things around and even compact the heap and your references | magically still point to what they did before! | tines wrote: | > Yeah I learned that the hard way when I found out that | std::vector apparently feels free to move your objects | around in memory for you | | In standard terminology, this is described as "invalidating | iterators". There are a bunch of member functions in | std::vector that either do or don't invalidate iterators, | e.g. push_back(...) does but size() doesn't. And as the | name implies, if you call a function that invalidates | iterators, all your existing iterators/pointers/references | become invalid. | Kranar wrote: | Iterator invalidation is different from this. Iterator | invalidation, as the name suggests, only applies to | iterators. The problem OP was having had to do with | dangling references. | | Some containers guarantee references won't dangle on | mutation such as unordered_map. An unordered_map may | invalidate iterators if objects are added or removed, but | will never result in dangling references (unless the | object is removed). That is, it is safe to have a pointer | to an object owned by an unordered_map and continue using | that pointer even after an iterator to that same object | is invalidated. | tines wrote: | Ah, interesting. Even within that dichotomy, I'd think | that if references aren't invalidated on mutation, then | dereferencing iterators would be valid, but incrementing | them would not be. | einpoklum wrote: | Iterators are historically a generalization of pointers. | A pointer is a kind of iterator. | | I mean, literally. The iterator of an std::vector<T> (not | a bool vector) is a T*. | _huayra_ wrote: | This is why things like std::list still have value: | iterator stability. You even get this in most map/sets too. | | But vector is usually what one should reach for unless one | has a good reason not to. The only danger really comes when | the lifetime of iterators and the thing they point to | become a bit decoupled, even due to insertion! | einpoklum wrote: | You can also use an offset instead of a pointer (or a | pointer to the object and and an offset into its heap | storage, or whatever). | Dylan16807 wrote: | > told the computer it was safe | | > by _not_ deleting | | I think there's a problem here. | overgard wrote: | Well, the scene graph code wasn't code I wrote, and this was | back in 2014. But sure, obviously it was an error on my | part... we are talking about footguns afterall, I'm the one | that pulled the trigger. | secondcoming wrote: | No, _everyone_ gets LHS auto wrong at the start. They don 't | realise it makes a copy. I have to point it out every other | code review. | jcelerier wrote: | The rule is simple: auto behaves like a template parameter | Kranar wrote: | There is nothing simple about that (not to mention it's | not actually true either). | Koshkin wrote: | Ah, templates... Universal references... Good stuff. | Kranar wrote: | Yep, except now we're supposed to call them forwarding | references because the motivation Scott Meyers had for | naming them universal references turned out to be | incorrect in subtle ways. | | Even the notable experts get things wrong when it comes | to how complex and bloated C++ is. | suprfsat wrote: | Simply by using C++, you tell the computer all sorts of | things, such as "I know the entire language by heart", and "I | have a death wish". | owl57 wrote: | While C++ is a dangerous tool indeed, ignoring the rule of | 5 is analogous to ignoring the safety guidelines, not | simply using the tool. | yongjik wrote: | A C++ object can be perfectly safe to copy in one | situation and conjure up nasal demons in another | situation. I don't think rule of 5 would help in that | case. | decker wrote: | Sounds like the root cause was due to not obeying the rule of | five: | https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_program... | overgard wrote: | Probably! I don't remember the code exactly, this was back in | 2014 and I think most of the code I had to consume was | primarily written by a 24 year old out of college (smart of | course, but C++ takes a long time to get good at). | einpoklum wrote: | So, if I write: | | int x; auto y = x; | | do you expect y to be a reference? Surely not. | | No, my friend, auto is for values, not references. You want a | reference - you have to say so. | | (And same goes for rvalue references - auto&& .) | leetcrew wrote: | I too have fallen victim to this and similar traps. it's a good | idea to use a deleted copy constructor when you know it's not | safe to copy the object. you can't completely stop others (or | yourself) from doing bad things, but you can at least give them | a moment to reflect on what they are doing. | nyanpasu64 wrote: | I wish C++ copy constructors were explicit by default. | secondcoming wrote: | It's not about safety, it's about efficiency. It's usually an | unintended copy. For example, if your function `foo` returns | a `const std::string&` the code `auto x = foo()` creates a | copy. | jchw wrote: | As others have pointed out, the idiomatic solution here is to | delete the copy constructor if an instance is unsafe to copy -- | however, I suspect the _reason_ why auto behaves differently | from other inference in requiring this to be explicit is | probably something along the lines of making it harder to have | a dangling reference. It's shockingly easy to wind up with a | dangling reference with fairly innocuous code, something like | QString().toUtf8().data() so maybe this makes sense. (Doesn't | help for that case since it's a pointer to raw data, but you | get the picture.) | nyanpasu64 wrote: | Worse yet is an object which _is_ safe to copy in some cases, | but you intended to not copy it at the moment, and you create | a dangling reference to a field within. I wish C++ copy | constructors were explicit by default. (But declaring the | copy constructor explicit turns off aggregate initialization, | so I can 't do that.) | overgard wrote: | I think, all things considered, the way it works is probably | the best way, since you might want to use "auto" to actually | make a copy. It's just very surprising, because when I have | explicit types I'm used to looking for that sort of error, | but with an auto I wasn't (at the time) used to looking for | that. | dvt wrote: | The nested function execution interleaving blew my mind (imagine | having to debug that), so I had to look it up. Apparently, | interleaving is prohibited in C++17 onward. So this: | | > The second one is there since this is C++, priority() might | raise an exception, meaning that new Widget will be called, but | never passed to std::shared_ptr, and thus never deleted! | | Is now impossible (thank God!). See a full SO discussion here[1]. | Stuff like this makes me so happy I don't write C++ anymore, but | the gist of it is (from the standard): | | > For each function invocation F, for every evaluation A that | occurs within F and every evaluation B that does not occur within | F but is evaluated on the same thread and as part of the same | signal handler (if any), either A is sequenced before B or B is | sequenced before A. | | [1] https://stackoverflow.com/questions/38501587/what-are-the- | ev... | ncmncm wrote: | In three decades' use of C++, of all generations going back to | original cfront, I have never encountered any difficulty, | confusion, or actual problem arising from this phenomenon. It | is a favorite of language lawyers and people borrowing trouble. | TwoBit wrote: | > Is now impossible | | I don't see how that is so. C++ 17 allows new Widget to | complete and then the priority() call to execute and throw | before both are passed to shared_ptr(), thus creating a leak. | Your cited example [1] doesn't leak because both arguments are | shared_ptr. Seems to me that C++ 17 does indeed solve this | latter case but not the former. | nyanpasu64 wrote: | > both are passed to shared_ptr() | | priority() is _not_ passed to shared_ptr(), only to | processWidget(). | dvt wrote: | Per [1]: | | > evaluations of A and B are _indeterminately sequenced_ : | they may be performed in any order but may not overlap: | either A will be complete before B, or B will be complete | before A. The order may be the opposite the next time the | same expression is evaluated. | | And: | | > 21) Every expression in a comma-separated list of | expressions in a parenthesized initializer is evaluated as if | for a function call ( _indeterminately-sequenced_ ) | | Emphasis mine. What the above basically says is that in the | case of some function `f(A, B)`, the arguments `A`, and `B`, | are what's known as "indeterminately-sequenced" -- this mean | that their execution cannot be interleaved (overlap) -- but | they still individually execute in a non-deterministic order | (A before B, and sometimes B before A)! | | With that said, the good news is that B can now never throw | in the middle of A, which is precisely what we have in OP's | example. | | [1] https://en.cppreference.com/w/cpp/language/eval_order | _huayra_ wrote: | Whew thanks for looking this up! I was afraid I'd have to | add yet another entry to my gigantic C++ footguns.org | document. It's getting so big now that Emacs struggles a | bit to load it due to inline code examples! | shric wrote: | At first I thought that was a domain and not an org file. | :) | | Looks like footguns.com is taken but footguns.org is | still available. | dale_glass wrote: | My favorite: slicing. | | That one blew my mind when I found out. You can rip off a chunk | of an object by accident... and nothing happens whatsoever. | There's no warning from the compiler. | | You can stuff a Derived into a std::list<Base>, it'll work just | fine, and if Derived happened to say, hold a pointer, it'll | happily vanish into the ether. | | Besides the amount of confusion that can ensure from this, it | means you can have problems if you want to introduce inheritance | into a place that didn't have it before. | | Especially if it's some sort of external component. You think | you're clever inheriting from a framework class and adding some | data on top? Nope, the internal structure you don't control | doesn't want to cooperate with that plan. | user-the-name wrote: | Well, here we have an entire thread of nothing but reasons to | never write anything in C++. | | Jesus christ, what an utter mess of a language. | MaxBarraclough wrote: | One of my favourites too. Here's a good StackOverflow answer on | the topic. [0] Looking here [1] though, did something change in | C++17 to ameliorate things? _edit_ Turns out yes, see dvt 's | comment. _Second edit_ Apparently [2] I knew this 3 years ago and | forgot. | | [0] https://stackoverflow.com/a/7693775/ | | [1] The final bullet-point in the Notes section of | https://en.cppreference.com/w/cpp/memory/shared_ptr/make_sha... | | [2] https://stackoverflow.com/questions/38501587/what-are-the- | ev... | 2bitencryption wrote: | My favorite C++ footgun is creating a Vector with some items, | taking a reference to, say, &vec[3], then adding another item to | the vec, then trying to use the reference from the previous step. | | If you write C++ it might be obvious what the problem is. | | If you don't, this will absolutely ruin your entire day. | | The worst part is, 95% of the time, it will probably work without | issue. | | But eventually, pushing a new item to the Vector will trigger a | relocation of the whole vector, which will invalidate your | reference and bring down production. Have fun debugging that. | pizza234 wrote: | Interestingly, AFAIK also Golang suffers from something | similar: creating a slice from an array, then performing an | operation on the array, that causes resizing - the slice will | keep pointing to the old array data. | bentcorner wrote: | I can understand people used to a HLL running into this. It's | useful to read the documentation: | https://en.cppreference.com/w/cpp/container/vector/insert | mentions that references/iterators may be invalidated. | | I'm not going to pretend that I understand everything on that | site (particularly anything about complex templates and things | like SFINAE) but often there's comprehensible stuff in there. | It can be really helpful. | flyingswift wrote: | What is the safe way to achieve the same result? | TylerGlaiel wrote: | store the index 3 as an int instead of &vec[3] | [deleted] | Kranar wrote: | The concept behind this is reference stability, and if you | need a collection that has stable references, you must | introduce a level of indirection, that is, instead of a | vector<T>, you use a vector<unique_ptr<T>> and then you can | take references as follows: auto& r = | *some_vector[0]; | flyingswift wrote: | Thanks! I am just learning C++ for a new gig, and coming | from Javascript land, it is a lot to take in :) | yongjik wrote: | In addition to other answers, sometimes you do know the | final/max length of the vector when you construct it. In that | case reserve() can reserve the necessary space, and as long | as you stay under the limit all the addresses will remain | valid. | | (Though it's still pretty brittle, so you may want to add a | ton of comments to warn yourself in the future...) | kaik wrote: | I kid you not, I spent a full working day debugging this exact | same issue (taking a pointer to a vector element, before adding | more elements). Very obvious if you understand how C++ and | vectors work, yet it took me forever to realize, and it was | miserable... | nicoburns wrote: | Yep. I learnt about this when I was learning Rust (which makes | this a compile-time error). I was very glad I didn't have to | learn this and the 100 other things like that seem to exist in | C++ the hard way! | krylon wrote: | You can run into the same problem in C, using malloc/realloc. | realloc, in fact, makes for a nasty footgun, too (and remains, | of course, available in C++). | turminal wrote: | Yes, but unlike with realloc and a custom dynamic array, C++ | references and smart pointers and containers are half-smart | and do all sorts of things on their own. Knowing when they | will and when they will not be "smart" makes writing C++ | really difficult sometimes. | duped wrote: | This, and the entire class of iterator invalidation bugs that | force you to memorize which collections are ok for which | applications. | nyanpasu64 wrote: | Rust takes the approach of flat-out not letting you mutate | _any_ collection while you have _any_ references to its | contents. It eliminates all dangling pointer bugs... I don 't | know if it rules out any useful use cases of collections | _with_ iterator stability. I think any C++ collection holding | unique_ptr _is_ stable (pushing to the collection doesn 't | invalidate the target of the unique_ptr), and Rust doesn't | have an safe ergonomic way to achieve that (perhaps | Pin<Box<MagicCell<T>>>, but we don't yet have a MagicCell | that makes &mut MagicCell<T> not noalias). | duped wrote: | The underlying pointer to a unique_ptr won't be invalidated | but the iterator might. Consider if you had a vector of | unique_ptrs and inserted into it within a for loop. | Depending on the implementation of the iterator this may | not be sound (if it's an index, you're probably ok, if it's | a pointer, you're screwed). | | If you wanted to do the same in Rust it would be | Vec<Box<T>>. Mutating the collection won't invalidate the | pointers. | liquidify wrote: | Any raw new in code is a smell. I thought this was old news? | astrange wrote: | I've noticed that every few years when I look at C++ again, | everyone says this about whatever was correct last time. | _huayra_ wrote: | Jumping on the footgun bandwagon, one I had always known, but | never realized how bad of a design decision it was was "the c++ | rvalue lifetime disaster" [0]. The talk linked in [0] has a | different conclusion, but describes the problem well. | | Basically, the lifetime aspects of r/l-values are improperly | coupled to the "can I scavenge the internals?" aspect (via | const). R-values should not have been promoted to const-ref (I | think it was a legacy behavior?) because then this const-ref can | be passed around (e.g. returned) as if it were going to outlive | the original reference's frame! | | This was the first time I realized what a mess C++ can be | (although I still use it a lot). This is a subtle error that I | have seen seasoned experts miss in code review (especially in | templated code). You can't work around it like you can the | underperforming parts of the stdlib (Abseil and Folly help out a | lot!). This promotion choice of auto&& -> auto const& is really | baked into the language and I don't see a path to change it. | | The other "biggest foot cannon" has gotta be the incredibly | subtle ways one can violate the ABI, e.g. executable A linking | against libs B and C, each of which bundle an ABI-incompatible | implementation of the same parent dep D (versions 1.1 and 2.1). | You never know which type of object you're really passing around | with D::SomeType! | | [0] https://quuxplusone.github.io/blog/2020/03/04/rvalue- | lifetim... | rileymat2 wrote: | If you try to use inheritance, object slicing by far. | | I understand the practical reasons for it, but it feels very | wrong. | ncmncm wrote: | It only ever comes up in toy examples. Working programmers do | not encounter it, so in practice it is never the cause of a | problem. | yongjik wrote: | I think it's basically the same problem as unique_ptr? This is | the best explanation I've seen: https://herbsutter.com/gotw/_102/ | jeffbee wrote: | This way to call a function smells wrong anyway. A shared pointer | to a new value is nonsense as a parameter, because at the point | of the call the pointer is not shared, it is unique! Since a | shared pointer is implicitly constructible from a unique pointer, | this makes more sense, assuming there are other call sites that | really intend to share ownership: void | frob(std::shared_ptr<T>); main() { | frob(std::make_unique<T>()); } | ot wrote: | If you know that the pointer is going to be shared, it's more | efficient to use `make_shared` as it will use a single | allocation for both the object and the control block. | Koshkin wrote: | Well, usually you designate a pointer as 'shared' not only when | it happens to be already shared but also when it will be shared | later on. | Kranar wrote: | You've now forgone an important optimization opportunity for | absolutely no benefit. | throwaaskjdfh wrote: | Why does it seem like C++ is constantly replacing its subtle | hazards with even more subtle hazards? It's like they never go | away, they just turn into something more obscure. | plorkyeran wrote: | This is an example of a very old subtle hazard that has been | removed entirely (the ordering which causes problems is no | longer permitted in C++17). | [deleted] | AnimalMuppet wrote: | In addition to what plorkyeran said, "more obscure" can mean | either "harder to understand" or "less often encountered". The | second one is progress. | pshc wrote: | It's Stroustrup's Full Employment Theorem in action. | Kranar wrote: | Because the people involved with C++ have a toxic aversion to | learning from other languages. Anytime issues are brought up | the people involved with C++ standardization keep yapping the | line that "But C++ is not like <other language>." and then | proceed to implement a half-assed version of what other | languages have and then 2-3 years later people find all kinds | of footguns that could have simply been avoided had proper | research been performed. | | The big issue is that becoming involved in the C++ | standardization process is purposely arcane, requiring people | to physically travel to remote locations, miss time off work, | and spend a lot of money. There are literally substantial | language features added to C++ that are nothing more than the | work of maybe 3-4 people. These features could have benefited | enormously from having 100s, if not 1000s of potential | developers exercising use cases and contributing suggestions, | but the standardization process is heavily gated. | duped wrote: | I feel like this is a strawman. | | 9/10 times when there is resistance to a particular feature | it's because implementing it breaks the ABI. | | > requiring people to physically travel to remote locations, | miss time off work, and spend a lot of money. | | It's a different kind of barrier to entry, but I would be | surprised if the attendees of the standards meetings aren't | being compensated for it. | Koshkin wrote: | Well, no doubt it all comes from good intentions. (With which, | you know, the road to hell is paved.) | towergratis wrote: | The real problem is not specific to C++, memory or shared | pointers, but as the author mentions later, the fact that | "function parameters evaluation order is unspecified". | | The problem is similar in C as well. | | `printf("%d, %d", i++, i++);` will give you different results | depending on the compiler. | Koshkin wrote: | The situation is even worse, due to the possible interleaving | of the execution of function calls. | towergratis wrote: | The root cause is the same | Koshkin wrote: | Well, except that C does not have exceptions (at least not | in the way that C++ does). ___________________________________________________________________ (page generated 2021-06-22 23:00 UTC)