10 ms·
Genuinely curious on what people’s thoughts are on ever using manual memory management? Are there many large programs (outside of embedded) where it ever reall
by 3a2d29 4y ago
Genuinely curious on what people’s thoughts are on ever using manual memory management?
Are there many large programs (outside of embedded) where it ever really makes sense?
I ask because I am a fan of C, and I am sure there will always be old legacy C code, but is manual memory management essentially antiquated?
- mbrodersen 4y agoYes. It is absolutely needed for high performance computing. Carefully controlling memory layouts down to the bit level is key to making code run fast on modern CPUs. There is a reason why C++ absolutely dominates the AAA game industry for example: every millisecond counts.
- hansvm 4y agoManual memory management is important if GC will blow your latency, throughput, or jitter budget. Keep in mind that latency can easily be amplified by artificially increasing contention or any number of other issues. Even something as simple as a constraint solver can easily find that memory management overhead dominates if they don't do something interesting. Work per deallocation can be extremely low in that domain, and anything like checkpointing can make the new memory access patterns sufficiently non-trivial that the GC isn't likely to be able to recognize it. A lot of software probably doesn't care though I don't think.
- deleted 4y ago[deleted]
- criddell 4y agoSome C compiler have a language extension to help with cleanup to help with memory and resource leaks. It lets you do RAII-type resource management that is popular in languages like C++ but the C version is way uglier. https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization#Compiler_%22cleanup%22_extensions https://en.wikipedia.org/wiki/Resource_acquisition_is_initia...
- cyber_kinetist 4y agoRAII solves only a fraction of the various scenarios you encounter with memory/resource management. It helps when you're temporarily creating and then destroying a resource right after, but these only hold for very simple use cases and doesn't help with much more complex real-world scenarios. (Example: you'll understand this when you have experience with any graphics APIs, RAII wrappers don't really help you that much and will actually make your life way harder...) My solution for general resource management, is to manage your resources manually on a central store (as you should be), get references to these objects using either pointers or indices, and use techniques like generational references to catch use-after-free scenarios at runtime. Makes things way simpler and performant.
- lenkite 4y agoThis is basically memory arenas right ? How does one handle problems as described here https://www.celonis.com/blog/c-memory-arenas-and-their-implications/ https://www.celonis.com/blog/c-memory-arenas-and-their-impli..., where memory appears to not be returned to the OS ?
- Thorrez 4y agoIn that blog, the arena is managed by the underlying library, and the programmer has no control over when memory is returned, and sometimes the underlying library keeps the memory around when the programmer doesn't want it to. It sounds like cyber_kinetist is talking about manually managing the arena, so then the programmer is in direct control of when the memory is returned, so the problem won't happen.
- verdagon 4y agoI think it makes sense for some segments of the programming world and not others. On Google Earth, garbage collection pauses weren't really acceptable, but we also wanted to add new functionality without being slowed down. Rust is great for a lot of reasons, but as many know, it can be slower on the feature velocity front. In some AAA games, they need much more flexibility than the borrow checker thinks is appropriate. At some point, one has so many unsafe blocks that undermine the guarantees of surrounding code, that it makes more sense to use a different paradigm. I know it's taboo to say this, but in many real world use cases, memory safety bugs aren't that bad and have the severity of any other logic error. Having a few more bugs per release might be worth it, if it means not dealing with the borrow checker's restrictions around abstraction, encapsulation, and polymorphism. You just fix them and push a new release. Also, the problem is nicely mitigated by tools like Address Sanitizer, and will get even better with upcoming advances such as memory tagging and generational references, all which detect memory problems with surprising efficacy and accuracy. In short, manual memory management is the right choice when Rust's drawbacks are a bit too much, and benefits aren't enough, for a particular situation. Just my two cents!
- Kranar 4y ago>I know it's taboo to say this, but in many real world use cases, memory safety bugs aren't that bad and have the severity of any other logic error. I don't think it's true of many real world use cases and if anything is only true of perhaps trivial use cases, where an application is run on a computer that has no other sensitive information and is not connected to the Internet. I think what's true is that many programs don't ever reach a point where they're popular enough that someone can reliably exploit it. The threat of memory related errors has not much to do with use case and more to do with whether someone cares enough to identify that error, can reliably reproduce it, and whether the application that exhibits that error is on enough computers that exploiting it is profitable. If those three conditions are satisfied then it matters not whether the program is a video game, a word processor, an image viewer or whatever.
- throwawaymaths 4y ago> I don't think it's true of many real world use cases and if anything is only true of perhaps trivial use cases, If you're running a single threaded cloud lambda, you could allocate into an arena and never free, so you don't get UAF or DF.
- BenFrantzDale 4y agoAs a C++ fan, I’d say manual memory control isn’t going anywhere but manually calling malloc and free is ridiculous in 2022 and beyond when `std::unique_ptr` Provides a better abstraction.
- jstimpfle 4y agoI'm a C programmmer and I rarely call malloc() and free(). Most memory is allocated in more global contexts that comprises many smaller allocastions. Such contexts get freed at appropriate, more global points, if at all. Often the implicit releasing of memory on process exit, done by the OS, is sufficient. This way removes the need to do memory management on a small scale. The problem I see personally with unique_ptr is that it's still doing small-scale memory management. It only removes some of the typing. (And for non memory management related tasks, it add significant amount of typing as well, compared to naked pointers).
- Xorlev 4y agoI can't recall the last time I used malloc/free in C++. Even new/delete are rare acquaintances, I use new most often in unit tests. As far as I'm concerned, it's automatic memory management (with lots of control). There's also the other end of the automatic memory management spectrum, garbage collection.
- pjmlp 4y agoIt is incredible how even something basic like new, which most C predecessors already had, isn't never going to be part of it, manually calculating heap allocations for 50 years.
- pistachiopro 4y agoAs a longtime C++ programmer, my view is that that manual memory management is a discipline that you can learn somewhat easily and then you don't spend a lot of time thinking about it.* <-- Big Asterisk For many applications, allocations fall into two categories: One, temporary allocations that work well with a stack allocator (which is often going to be the main stack where the allocations and deallocations are handled automatically, but sometimes it's useful to have secondary stacks with different lifetimes). And two, larger allocation categories where you can pre-plan access. Many many things can just be allocated statically, others can be allocated from simple pools, and maybe a few things will need complex pools, or even compacting allocators where you end up using smart pointers or smart handles to track them. Once you're used to programming this way, figuring out allocators usually isn't where you spend most of your time. (And as others have said in this thread, if performance is a concern, you spend the same amount of time thinking about this stuff in managed languages, as well.) * The Big Asterisk is that it's hard to program this way 100% correctly. While it's not hard to get code that works 99.999% of the time for normal usage, it's very hard to make sure nothing in your manually memory managed code is exploitable. Rust, for instance, adds a bunch of language features to fix this issue, and that's one interesting way to go. I do wonder, however, if for many applications it won't turn out that the sandboxing model of something like WASM is more productive. You can still code everything in good ol' C++, but the worst an exploiter can do is hit the edge of the sandbox and crash your application, instead of pwning the user's system.
- kaba0 4y ago> but the worst an exploiter can do is hit the edge of the sandbox and crash your application, instead of pwning the user's system. That is still exploitable. Let’s say you have a web app and the js code interacts with the wasm output - if the latter is exploitable, js code may be as well and it can be catastrophic if it is some deeply personal thing, or your bank account. And that can all be controlled by data alone, e.g. if it is a PDF converter or something.
- kllrnohj 4y agoFully manually managed memory is definitely antiquated / obsolete. As others have mentioned, you really shouldn't even be doing new/delete in C++ anymore, but instead std::make_unique or std::make_shared. At which point it's mostly automatic memory management. HOWEVER, partial manually managed memory is unlikely to go anywhere. It's just too powerful of an optimization tool, especially as memory is one of the things that's just not getting much faster. If you're trying to optimize something like a game engine, being able to traverse your objects as fast as possible means they need to be laid out in memory sequentially. This is being somewhat branded "data oriented design" in the game world ( https://en.wikipedia.org/wiki/Data-oriented_design https://en.wikipedia.org/wiki/Data-oriented_design ), but it's broadly applicable. Similarly you'll see things like arena allocators in all sorts of places (like protobufs https://developers.google.com/protocol-buffers/docs/reference/arenas https://developers.google.com/protocol-buffers/docs/referenc... ). This kinda blurs the line between "manual memory management" and "garbage collection" since it's sort of both, depending on which side of the usage you're on (individual allocations end up essentially GC'd, but the entire heap is manually managed). Again, it's just too fast of a tool to lose. The ability to jump between worlds is a very strong strength of C++, and it's something Rust can mostly pull off as well with the unsafe escape hatch.
- kaba0 4y ago> The ability to jump between worlds I feel like it is not possible from low level language, but contrary, it is possible from managed languages. It is easier to give a small escape hatch in a GCd language to allocate n bytes and manipulate it as if you were in C, then to add some hacky semi-GC to C/C++/Rust. Don’t get me wrong, unqiue pointers are really cool and useful, but shared pointers for example can’t be that painfully implemented without full control over execution (thinking of circular dependencies here).
- kllrnohj 4y agoManaged languages almost never offer comparable escape hatches. Eg, it's not possible to do anything interesting at all with the JVM. Nor with JS. Nor with Python. Etc.. None of them let you do either placement allocations nor arena allocations. .NET at least lets you do a linear array of structs, but that's nearly it.
- jandrewrogers 4y agoIt somewhat depends on what you mean by "manual memory management", I am not sure it has a definition beyond garbage collectors being outside it -- gray areas exist. Using that tool requires some mastery of its idioms and rules regardless. Memory management is much more complicated than safely acquiring and releasing memory for object instantiation, or even accessing it safely. At least in C++, you can build safe memory abstractions for almost any resource model, including some pretty unusual ones that show up in performance-sensitive applications. In C it is significantly more difficult. If you are building large-scale, high-performance infrastructure, manual memory management is to some extent unavoidable. In many database engines, for example, memory lifetimes and object lifetimes are distinct and independent concepts. There are classes of algorithm optimization that require strict and explicit understanding of the address space your process claims to own. The silicon does not respect the object model of your programming language, but you still need the silicon to manipulate memory directly (as it understands it) for performance reasons. This seems complicated but understanding it well enough to leverage it has significant performance implications. That said, I don't see a lot of heap allocation in things like database engines. Almost all the memory used is allocated and deployed at bootstrap, and most of the remainder is on the stack. Not a lot of attack surface for memory management problems to show up. Other applications may be different.
- kaba0 4y ago> Not a lot of attack surface for memory management problems to show up. A single bad pointer arithmetic, out of bounds access can wreak absolute havoc.
- wahern 4y agoI use C alot, will defend many of its finer qualities, and have little problem with manual memory management, per se, including using techniques like bump allocators and arenas. (I'm very peculiar about APIs, though. For example, I have my own GNU obstack-like library, but designed to permit building strings byte-by-byte.) That said, to my mind "large program" and "manual memory management" do not go together. It's difficult to comprehensively reason about large programs in toto in any language. In some respects C is better than many other languages in this regard as there are only a few ways components can reasonably fit together--not many features and complex semantics to cut through. However, when it comes to manual memory management in C it's effectively impossible to reason about the program as a whole, even if all other traps, like aliasing, are taken out of the picture. So even if I have a large application written entirely in C, it's never built that way, and that's not how I will conceive of it. It's built of discrete components with very minimal--preferably zero in most cases--cross dependencies. In fact, each component is typically maintained as either a single source file or an entirely separate library so that from soup-to-nuts the boundaries (functional and technical) are consistent. Components themselves, even single-file components, will typically be structured similarly internally. I try to avoid utility libraries with ad hoc functionality or even narrow, cross-cutting functionality. And I emphasize strictly functional interface boundaries. So, for example, if I use a bump allocator in a functional component, this fact is completely opaque and irrelevant outside the component. All of this sometimes results in more code duplication than people might normally be comfortable with. But it makes it far easier to reason about each component in isolation, and then in turn reason about their interactions. A language like Rust is literally built around this type of code flow and data discipline. So in many respects I'm making a strong argument for using Rust instead of C. If you stopped reading here, no problem. But I would still point out that this type of architectural discipline has many other benefits, like making it easier to mix-and-match languages (e.g. C + Lua + Swift) or to do major refactors. Some of the rich semantics of Rust and its ecosystem can easily lead one stray. To my way of thinking, something like Cargo is almost all liability for projects that expect any longevity. And while I think generational memory arenas are awesome, if and when the semantics of the arena leak beyond a functional interface boundary (whether "internal" or "external"--rarely a substantive distinction to me), then that presents a major problem. IOW, there shouldn't be "large programs" in any language. There should just be a bunch of small programs revolving around a central, cohesive problem model. (A high-level functional problem, not a technical problem like memory management.) And that should hold even if your final artifact is an enormous, static binary. One of the major benefits is that you're not stuck choosing just one memory management technique or even one language. One component can employ a bump allocator in a streaming parser, and another written in a language with mark & sweep GC. And if you enforce strict functional and semantic boundaries so these choices can't leak, you can often mix-and-match them with relative ease. One implication is that sometimes it could be more prudent to write a particular component in C rather than Rust, such as if the Rust component would end up a giant ball of unsafe{} blocks. This perspective is rather peculiar and unpopular these days, however. When baseline assumptions are an autocompleting IDE and seamless importation of all other software in an ecosystem, the calculus is entirely foreign. So I wouldn't expect it to resonate with many people.
- fatherzine 4y agoGreat idea for specialized kernels where performance is critical. Poor idea for mundane glue code, needlessly slowing down reading/writing code with trivial detail. In practice, at least 95% of the code out there is quite mundane. Python got it mostly right, though a language with a smoother transition between 'manual memory managed kernels' and 'general purpose glue' is still missing. Rust has a great type system and handles manual memory management remarkably well. Sadly, it is missing the garbage-collected yin to complement its manually managed yang.
- jcelerier 4y agoThe huge majority of c++ programs should not have any form of memory management - either you're managing values and doing it with smart pointers, containers, etc. as provided by std, boost, absl, folly or whatever, works fine and you never have to type "delete", or you 're managing OOP-ish objects and for this, Qt's hierarchical memory management scheme works great (and I guess is implemented in other toolkits too)
- flohofwoe 4y agoI wouldn't call it "manual memory management", but "explicit memory management" (e.g. to have complete control when and where memory allocation/freeing calls happen, and that memory management is "obviously visible" in the code). Explicit memory management is very important when performance matters and must be predictable (for instance in "soft realtime" applications like games), you want to get the expensive memory management calls out of the hot paths as much as possible and the language must provide the tools to make this possible. When seen under this lense (that memory management should be explicit and not hidden from the programmer), there isn't all that much difference between C, Zig, C++ or Rust, all those languages provide the features for explicit memory management, even if some also provide optional features for automatic memory management.
- pjmlp 4y agoThey have a place and time, when doing drivers and low level kernel stuff, configuring DMA buffers, for everything else, there have been better alternatives since Xerox PARC showed how a graphical workstation is supposed to be like. Even C++ RAII is already an improvement over spreading the code with malloc/free, since 40 years by now.