Most active commenters
  • mwkaufma(6)
  • elteto(4)
  • jandrewrogers(3)
  • WD-42(3)
  • tialaramex(3)
  • theICEBeardk(3)
  • petermcneeley(3)

←back to thread

327 points AareyBaba | 77 comments | | HN request time: 0.4s | source | bottom
1. mwkaufma ◴[] No.46183728[source]
TL;DR

- no exceptions

- no recursion

- no malloc()/free() in the inner-loop

replies(9): >>46183820 #>>46183900 #>>46184073 #>>46184113 #>>46184198 #>>46184398 #>>46184472 #>>46184588 #>>46185500 #
2. jandrewrogers ◴[] No.46183820[source]
i.e. standard practice for every C++ code base I've ever worked on
replies(1): >>46183866 #
3. DashAnimal ◴[] No.46183866[source]
What industry do you work in? Modern RAII practices are pretty prevalent
replies(2): >>46183904 #>>46184092 #
4. wiseowise ◴[] No.46183900[source]
That’s hardly 90% of C++.
replies(2): >>46183975 #>>46184047 #
5. jandrewrogers ◴[] No.46183904{3}[source]
What does RAII have to do with any of the above?
replies(4): >>46183995 #>>46184069 #>>46184097 #>>46184150 #
6. bluGill ◴[] No.46183975[source]
Large parts of the standard library malloc/free.
replies(2): >>46183988 #>>46184076 #
7. canyp ◴[] No.46183988{3}[source]
Or throw.
8. WD-42 ◴[] No.46183995{4}[source]
0 allocations after the program initializes.
replies(4): >>46184066 #>>46184067 #>>46184129 #>>46184331 #
9. elteto ◴[] No.46184047[source]
If you compile with -fno-exceptions you just lost almost all of the STL.

You can compile with exceptions enabled, use the STL, but strictly enforce no allocations after initialization. It depends on how strict is the spec you are trying to hit.

replies(2): >>46184298 #>>46184314 #
10. nicoburns ◴[] No.46184066{5}[source]
RAII doesn't necessarily require allocation?
11. Gupie ◴[] No.46184067{5}[source]
Open a file in the constructor, close it in the destructor. RAII with 0 allocations.
replies(1): >>46187701 #
12. nmhancoc ◴[] No.46184069{4}[source]
Not an expert but I’m pretty sure no exceptions means you can’t use significant parts of std algorithm or the std containers.

And if you’re using pooling I think RAII gets significantly trickier to do.

replies(1): >>46184330 #
13. Taniwha ◴[] No.46184073[source]
yup, same for any real time code, new/malloc/free/delete use hidden mutexes and can cause priority inversion as a result - heisenbugs, that audio/video dropout that happens rarely and you can't quite catch - best to code to avoid them
replies(1): >>46184539 #
14. gmueckl ◴[] No.46184076{3}[source]
But you won't miss those parts much if all your memory is statically initialized at boot.
15. Cyan488 ◴[] No.46184092{3}[source]
This is common in embedded systems, where there is limited memory and no OS to run garbage collection.
replies(1): >>46187834 #
16. DashAnimal ◴[] No.46184097{4}[source]
Well if you're using the standard library then you're not really paying attention to allocations and deallocations for one. For instance, the use of std::string. So I guess I'm wondering if you work in an industry that avoids std?
replies(1): >>46184756 #
17. thefourthchime ◴[] No.46184113[source]
I've worked on a playout system for broadcast television. The software has to run for years at a time and not have any leaks, We need to send out one frame of television exactly on time, every time.

It is "C++", but we also follow the same standards. Static memory allocation, no exceptions, no recursion. We don't use templates. We barely use inheritance. It's more like C with classes.

replies(1): >>46184565 #
18. tialaramex ◴[] No.46184129{5}[source]
RAII doesn't imply allocating.

My guess is that you're assuming all user defined types, and maybe even all non-trivial built-in types too, are boxed, meaning they're allocated on the heap when we create them.

That's not the case in C++ (the language in question here) and it's rarely the case in other modern languages because it has terrible performance qualities.

19. astrobe_ ◴[] No.46184150{4}[source]
And what does "modern" has to do with it anyway.
20. tialaramex ◴[] No.46184198[source]
Forbidding recursion is pretty annoying. One of the nice things that's on the distant horizon for Rust is an explicit tail recursion operator perhaps named `become`. Unlike naive recursion, which as this video (I haven't followed the link but I'm assuming it is Laurie's recent video) explains risks stack overflow, optimized tail recursion doesn't grow the stack.

The idea of `become` is to signal "I believe this can be tail recursive" and then the compiler is either going to agree and deliver the optimized machine code, or disagree and your program won't compile, so in neither case have you introduced a stack overflow.

Rust's Drop mechanism throws a small spanner into this, in principle if every function foo makes a Goose, and then in most cases calls foo again, we shouldn't Drop each Goose until the functions return, which is too late, that's now our tail instead of the call. So the `become` feature AIUI will spot this, and Drop that Goose early (or refuse to compile) to support the optimization.

replies(3): >>46184322 #>>46185348 #>>46192161 #
21. vodou ◴[] No.46184298{3}[source]
Not my experience. I work with a -fno-exceptions codebase. Still quite a lot of std left. (Exceptions come with a surprisingly hefty binary size cost.)
replies(2): >>46184320 #>>46184674 #
22. theICEBeardk ◴[] No.46184314{3}[source]
Are you aware of the Freestanding definition of STL? See here: https://en.cppreference.com/w/cpp/freestanding.html Large and useful parts of it are available if you run with a newer c++ standard.
replies(1): >>46184623 #
23. theICEBeardk ◴[] No.46184320{4}[source]
Apparently according to some ACCU and CPPCon talks by Khalil Estel this can be largely mitigated even in embedded lowering the size cost by orders of magnitude.
replies(2): >>46184444 #>>46184552 #
24. tgv ◴[] No.46184322[source]
In C, tail recursion is a fairly simple rewrite. I can't think of any complications.

But ... that rewrite can increase the cyclomatic complexity of the code on which they have some hard limits, so perhaps that's why it isn't allowed? And the stack overflow, of course.

replies(1): >>46184519 #
25. theICEBeardk ◴[] No.46184330{5}[source]
https://en.cppreference.com/w/cpp/freestanding.html to see the parts you can use.
26. jjmarr ◴[] No.46184331{5}[source]
Stack "allocations" are basically free.
replies(1): >>46189939 #
27. krashidov ◴[] No.46184398[source]
Has anyone else here banned exceptions (for the most part) in less critical settings (like a web app)?

I feel like that's the way to go since you don't obscure control flow. I have also been considered adding assertions like TigerBeetle does

https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TI...

replies(3): >>46184593 #>>46185874 #>>46185984 #
28. Espressosaurus ◴[] No.46184444{5}[source]
Yeah. I unfortunately moved to an APU where code size isn't an issue so I never got the chance to see how well that analysis translated to the work I do.

Provocative talk though, it upends one of the pillars of deeply embedded programming, at least from a size perspective.

29. msla ◴[] No.46184472[source]
At that point, why not write in C? Do they think it's C/C++ and not understand the difference?

> no recursion

Does this actually mean no recursion or does it just mean to limit stack use? Because processing a tree, for example, is recursive even if you use an array, for example, instead of the stack to keep track of your progress. The real trick is limiting memory consumption, which requires limiting input size.

replies(4): >>46184605 #>>46184960 #>>46185419 #>>46187438 #
30. AnimalMuppet ◴[] No.46184519{3}[source]
I don't know that it's just cyclomatic complexity. I think it at least part of it is proving that you meet hard real-time constraints. Recursion is harder to analyze that way than "for (i = 0; i < 16; i++) ... " is.
31. AnimalMuppet ◴[] No.46184539[source]
They also can simply fail, if you are out of memory or your heap is hopelessly fragmented. And they take an unpredictable amount of time. That's very bad if you're trying to prove that you satisfy the worst-case timing requirement.
32. vodou ◴[] No.46184552{5}[source]
Need to check it out. I guess you mean these:

- C++ Exceptions Reduce Firmware Code Size, ACCU [1]

- C++ Exceptions for Smaller Firmware, CppCon [2]

[1]: https://www.youtube.com/watch?v=BGmzMuSDt-Y

[2]: https://www.youtube.com/watch?v=bY2FlayomlE

33. EliRivers ◴[] No.46184565[source]
I worked on the same for many years; same deal - playout system for broadcast, years of uptime, never miss a frame.

The C++ was atrocious. Home-made reference counting that was thread-dangerous, but depending on what kind of object the multi-multi-multi diamond inheritance would use, sometimes it would increment, sometimes it wouldn't. Entire objects made out of weird inheritance chains. Even the naming system was crazy; "pencilFactory" wasn't a factory for making pencils, it was anything that was made by the factory for pencils. Inheritance rather than composition was very clearly the model; if some other object had function you needed, you would inherit from that also. Which led to some object inheriting from the same class a half-dozen times in all.

The multi-inheritance system given weird control by objects on creation defining what kind of objects (from the set of all kinds that they actually were) they could be cast to via a special function, but any time someone wanted one that wasn't on that list they'd just cast to it using C++ anyway. You had to cast, because the functions were all deliberately private - to force you to cast. But not how C++ would expect you to cast, oh no!

Crazy, home made containers that were like Win32 opaque objects; you'd just get a void pointer to the object you wanted, and to get the next one pass that void pointer back in. Obviously trying to copy MS COM with IUnknown and other such home made QueryInterface nonsense, in effect creating their own inheritance system on top of C++.

What I really learned is that it's possible to create systems that maintain years of uptime and keep their frame accuracy even with the most atrocious, utterly insane architecture decisions that make it so clear the original architect was thinking in C the whole time and using C++ to build his own terrible implementation of C++, and THAT'S what he wrote it all in.

Gosh, this was a fun walk down memory lane.

replies(2): >>46185057 #>>46186157 #
34. pton_xd ◴[] No.46184588[source]
That's standard in the games industry as well. Plus many others like no rtti, no huge dependencies like boost, no smart pointers, generally avoid ctors / dtors, etc.
35. mwkaufma ◴[] No.46184593[source]
Lots of games, and notably the Unreal Engine, compile without exceptions. EASTL back in the day was in part written to avoid the poor no-exception support in Dinkumware STL and STLport.
replies(1): >>46186856 #
36. mwkaufma ◴[] No.46184605[source]
For a long time, at least in MS and Intel, the C++ compilers were better than the C compilers.
37. elteto ◴[] No.46184623{4}[source]
Well, it's mostly type definitions and compiler stuff, like type_traits. Although I'm pleasantly surprised that std::tuple is fully supported. It looks like C++26 will bring in a lot more support for freestanding stuff.

No algorithms or containers, which to me is probably 90% of what is most heavily used of the STL.

38. elteto ◴[] No.46184674{4}[source]
Not exactly sure what your experience is, but if you work with in an -fno-exceptions codebase then you know that STL containers are not usable in that regime (with the exception of std::tuple it seems, see freestanding comment below). I would argue that the majority of use cases of the STL is for its containers.

So, what exact parts of the STL do you use in your code base? Most be mostly compile time stuff (types, type trait, etc).

replies(1): >>46185134 #
39. jandrewrogers ◴[] No.46184756{5}[source]
I work in high-scale data infrastructure. It is common practice to do no memory allocation after bootstrap. Much of the standard library is still available despite this, though there are other reasons to not use the standard containers. For example, it is common to need containers that can be paged to storage across process boundaries.

C++ is designed to make this pretty easy.

40. mwkaufma ◴[] No.46184960[source]
Re: recursion. She explains in her video. Per requirements, the stack capacity has to be statically verifiable, and not dependent on runtime input.
41. webdevver ◴[] No.46185057{3}[source]
it is also interesting that places where you would expect to have quite 'switched-on' software development practices tend to be the opposite - and the much-maligned 'codemonkeys' at 'big tech' infact tend to be pretty damn good.

it was painful for me to accept that the most elite programmers i have ever encountered were the ones working in high frequency trading, finance, and mass-producers of 'slop' (adtech, etc.)

i still ache to work in embedded fields, in 8kB constrained environment to write perfectly correct code without a cycle wasted, but i know from (others) experience that embedded software tends to have the worst software developers and software development practices of them all.

42. alchemio ◴[] No.46185134{5}[source]
You can use std containers in a no-exceptions environment. Just know that if an error occurs the program will terminate.
replies(2): >>46186937 #>>46188021 #
43. zozbot234 ◴[] No.46185348[source]
The tail recursion operator is a nice idea, but the extra `become` keyword is annoying. I think the syntax should be `return as`: it uses existing keywords, is unambiguous and starts with `return` which tail recursion is a special case of.
replies(1): >>46185705 #
44. drnick1 ◴[] No.46185419[source]
You may still want to use classes (where they make sense), references (cleaner syntax than pointers), operator overloading, etc. For example, a linear algebra library is far nicer to write and use in C++.
replies(1): >>46186889 #
45. petermcneeley ◴[] No.46185500[source]
This is basically video games prior to 2010
replies(1): >>46185925 #
46. tialaramex ◴[] No.46185705{3}[source]
Traditionally the time for bike shedding the exact syntax is much closer to stabilization.

Because Rust is allowed (at this sort of distance in time) to reserve new keywords via editions, it's not a problem to invent more, so I generally do prefer new keywords over re-using existing words but I'm sure I'd be interested in reading the pros and cons.

replies(1): >>46186040 #
47. tonfa ◴[] No.46185874[source]
Google style bans them: https://google.github.io/styleguide/cppguide.html#Exceptions
48. mwkaufma ◴[] No.46185925[source]
Relax the dynamic-memory restriction to "limit per-event memory allocation to the bump allocator" and it's still mostly true for many AAA/AAAA games I work on today.
replies(1): >>46186204 #
49. fweimer ◴[] No.46185984[source]
Most large open-source projects ban exceptions, often because the project was originally converted from C and is just not compatible with non-local control flow. Or the project originated within an organization which has tons of C++ code that is not exception-safe and is expected to integrate with that.

Some large commercial software systems use C++ exceptions, though.

Until recently, pretty much all implementations seemed to have a global mutex on the throw path. With higher and higher core counts, the affordable throw rate in a process was getting surprisingly slow. But the lock is gone in GCC/libstdc++ with glibc. Hopefully the other implementations follow, so that we don't end up with yet another error handling scheme for C++.

50. zozbot234 ◴[] No.46186040{4}[source]
The usual argument against a decorated `return` keyword is that a proper tail call is not a true "return" since it has to first drop any locals that aren't passed thru to the tail call. I don't think it's a very good argument because if the distinction of where exactly those implicit drops occur was that important, we'd probably choose to require explicit drops anyway.
51. uecker ◴[] No.46186157{3}[source]
A multi-inhertiance system is certainly not something somebody who "was thinking in C" would ever come up with. This sounds more like a true C++ mess.
replies(1): >>46186537 #
52. petermcneeley ◴[] No.46186204{3}[source]
Developers have gotten lazy. Im glad to here where you are they are at least still trying.
replies(1): >>46186257 #
53. mwkaufma ◴[] No.46186257{4}[source]
Nah I'm lazy, too.
replies(1): >>46186486 #
54. petermcneeley ◴[] No.46186486{5}[source]
but_you_were_the_chosen_one.jpeg
55. throwaway2037 ◴[] No.46186537{4}[source]
I worked on a pure C system early in my career. They implemented multiple inheritance (a bit like Perl/Python MRO style) in pure C. It was nuts, but they didn't abuse it, so it worked OK.

Also, serious question: Are they any GUI toolkits that do not use multiple inheritance? Even Java Swing uses multiple inheritance through interfaces. (I guess DotNet does something similar.) Qt has it all over the place.

replies(4): >>46186606 #>>46187923 #>>46188011 #>>46190445 #
56. nottorp ◴[] No.46186606{5}[source]
One could say toolkits done in C++ use multiple inheritance because C++ doesn't have interfaces though.
57. jesse__ ◴[] No.46186856{3}[source]
Basically all high profile engine teams I know of ban exceptions. They're worse than useless
58. jesse__ ◴[] No.46186889{3}[source]
Function overloading is nice, too
59. elteto ◴[] No.46186937{6}[source]
So you can’t use them then.
replies(2): >>46189176 #>>46203535 #
60. billforsternz ◴[] No.46187438[source]
Semi serious idea: A lot of people (including me) write C++ but it's basically C plus a small set of really ergonomic and useful C++ features (eg references). This should be standardised as a new language called C+
replies(1): >>46190794 #
61. dh2022 ◴[] No.46187701{6}[source]
std::vector<int> allocated and freed on the stack will allocate an array for its int’s on the heap…
replies(2): >>46189144 #>>46193524 #
62. criddell ◴[] No.46187834{4}[source]
Garbage collection in C++?
63. aninteger ◴[] No.46187923{5}[source]
The best example I can think of is the Win32 controls UI (user32/Create window/RegisterClass) in C. You likely can't read the source code for this but you can see how Wine did it or Wine alternatives (like NetBSD's PEACE runtime, now abandoned).

Actually the only toolkit that I know that sort of copied this style is Nakst's Luigi toolkit (also in C).

Neither really used inheritance and use composition with "message passing" sent to different controls.

64. WD-42 ◴[] No.46188011{5}[source]
GTK does not support multiple inheritance afaik.
replies(1): >>46188124 #
65. WD-42 ◴[] No.46188021{6}[source]
We've banned exceptions! If any occur, we just don't catch them.
66. aninteger ◴[] No.46188124{6}[source]
It doesn't but it definitely "implements" a single inheritance tree (with up casting/down casting) which I believe Xt toolkits (like Motif) also did.
67. usefulcat ◴[] No.46189144{7}[source]
I've heard that MSVC does (did?) that, but if so that's an MSVC problem. gcc and clang don't do that.

https://godbolt.org/z/nasoWeq5M

replies(1): >>46189608 #
68. _flux ◴[] No.46189176{7}[source]
I don't think it would be typical to depend on exception handling when dealing with boundary conditions with C++ containers.

I mean .at is great and all, but it's really for the benefit of eliminating undefined behavior and if the program just terminates then you've achieved this. I've seen decoders that just catch the std::out_of_range or even std::exception to handle the remaining bugs in the logic, though.

69. menaerus ◴[] No.46189608{8}[source]
WDYM? Vector is an abstraction over dynamically sized arrays so sure it does use heap to store its elements.
replies(2): >>46195886 #>>46205474 #
70. grougnax ◴[] No.46189939{6}[source]
No. And they're unsafe. Avoid them at all costs.
71. uecker ◴[] No.46190445{5}[source]
I take this back ;-) People come up with crazy things. Still I would not call this "C thinking". Building object-oriented code in C is common though and works nicely.
72. zeroc8 ◴[] No.46190794{3}[source]
That probably would see more success than the monster they've created. I've been out of the C++ world for a while, but I hardly recognize the language anymore.
73. morshu9001 ◴[] No.46192161[source]
Thinking recursively is one thing, but can't remember the last time I've wanted to use recursion in real code.
74. Gupie ◴[] No.46193524{7}[source]
Sure, but my point was that RAII doesn't need to involve the heap. Another example would be acquiring abd releasing a mutex.
75. aw1621107 ◴[] No.46195886{9}[source]
I think usefulcat interpreted "std::vector<int> allocated and freed on the stack" as creating a default std::vector<int> and then destroying it without pushing elements to it. That's what their godbolt link shows, at least, though to be fair MSVC seems to match the described GCC/Clang behavior these days.
76. account42 ◴[] No.46203535{7}[source]
Of course you can, you just need to check your preconditions and limit sizes ahead of time - but you need to do that with exceptions too because modern operating systems overcommit instead of failing allocations and the OOM killer is not going to give you an exception to handle.
77. ◴[] No.46205474{9}[source]