Why I Write Games in C (yes, C)
jonathanwhiting.com
jonathanwhiting.com
I love this opinion from games programmers because they never qualify it and talk about what their latency budgets are and what they do in lieu of a garbage collector. They just hand wave and say "GC can't work". The reality is you still have to free resources, so it's not like the garbage collector is doing work that doesn't need to be done. What latency budgets are you working with? How often do you do work to free resources? What are the latency requirements there? Even at 144 fps, that's 7ms per frame. If you have a garbage collector that runs in 200us, you could run a GC on every single frame and use less than 3% of your frame budget on the GC pause. I'm -not- suggesting that running a GC on every frame is a good idea or that it should be done, but what I find so deeply frustrating is that the argument that GC can't work in a game engine is never qualified.
edit: wow for once the replies are actually good, very pleased with this discussion.
I don't think it's necessarily that it "can't" work as much as it takes away a critical element of control from the game developers and the times you find yourself "at the mercy" of the garbage collector is pretty damn frustrating.
The problem we ran into with garbage collection was that it was generally non-deterministic both in terms of when it would happen and how long it would take. We actually added an API hook in our JS to manually trigger GC (something you can do when you ship a custom JS runtime) so we could take at least the "when it happens" out of the picture.
That said, there were often fairly large variances in how long it would take and, while frame time budgets may seem to accommodate things, if you end up in a situation where one of your "heavy computation" frames coincides with a GC that runs long, you're going to get a nasty frame time spike.
We struggled enough that we discussed exposing more direct memory management through a custom JS API so we could more directly control things. We ultimately abandoned that idea for various reasons (though I left over 6 years ago so I have no idea how things are today).
This is basically it. People who have never worked on actual real-time systems just never seem to get that in those environments determinism often matters more than raw performance. I don't know about "soft" real-time (e.g. games, or audio/video) but in "hard" real-time (e.g. avionics, industrial control) it's pretty routine to do things like disable caches and take a huge performance hit for the sake of determinism. If you can run 10% faster but miss deadlines 0.1% more often, that's a fail. It's too easy for tyros to say pauses don't matter. In many environments they do.
Hah. Who sez that audio and video products have 'soft' real time? Go on now.
I started game programming on the Atari 800, Apple 2, TRS-80. Wrote NES games with 2k of ram. I wrote games in C throughout the 90s including games on 3DO and PS1 and at the arcade.
I was a GC hater forever and I'm not saying you can ignore it but the fact that Unity runs in C# with GC and that so many quality and popular shipping games exist using it is really proof that GC is not the demon that people make it out to be
Some games made with GC include Cuphead, Kerbal Space Program, Just Shapes & Beats, Subnautica, Ghost of a Tale, Beat Saber, Hollow Knight, Cities: Skylines, Broforce, Ori and the Blind Forest
You might spend more time fighting the GC than benefitting from it. And that seems to be the experience for large games - simpler ones might not care.
Unity offers a lot more than just a language, and developers have to choose, are they willing to put up with GC to get the rest of what Unity offers.
You're still at the mercy of the malloc implementation. I've seen some fairly nasty behaviour involving memory leaks and weird pauses on free coming from a really hostile allocation pattern causing fragmentation in jemalloc's internal data.
In fact, doing that is often a really bad idea in general because of the extreme importance of cache effects. In a high-performance game engine, you need to have a fine degree of control over where your game objects get placed, because you need to ensure your iterations are blazingly fast.
Games are very friendly to that approach- with a bit of thought you can use arenas and object pools to cover 99% of what you need, and cut out all of the failure modes of a general purpose GC or malloc implementation.
Instead my miniature library obviated pools by simply having binary operators operate directly on one of the vector objects passed to it, if more than one vector was required for the operation internally they would be "statically allocated" by defining them in the function definition's context (some variants i would also return one of these internal vectors - which was only safe to use until a subsequent call of the same operator!).
The result this had on the calling code looked quite out of place for JS, because you would effectively end up doing a bunch of static memory allocation by assigning a bunch of persistent vectors for each function in it's definition context, and then you would often need to explicitly reinitialize the vectors if they were expected to be zero.
... it was however super fast and always smooth - I wish it was possible to turn the GC off in cases like this when you know it's not necessary. It was more of a toy as a library, but i did write some small production simulations with it - i'm not sure how well the method would extend to comprehensive vector and matrix libraries, I think the main problem is that most users would not be willing to use a vector library this way, because they want to focus on the math and not have to think about memory.
The real run-time cost of memory management done well in a modern game engine written without OOP features is extremely low.
We usually use a few very simple specialized memory allocators, you'd probably be surprised by how simple memory management can be.
The trick is to not use the same allocator when the lifetime is different.
Some resources are allocated once and basically never freed.
Some resources are allocated per level and freed all at once at the end.
Some resources are allocated during a frame and freed all at once when a new frame starts.
And lastly, a few resources are allocated and freed randomly, and here the cost of fragmentation is manageable because we're talking about a few small chunks (like network packets)
Instead, we have different types of global arenas, bump allocators, etc. that you can use. These all pre-allocate memory once at start up, and... that's it.
When you have well defined allocation patterns, allocating a new "object" is just a "last += 1;` and once you are done you deallocate thousands of objects by just doing `last -= size();`.
That's ~0.3 nanoseconds per allocation, and 0.x nano-seconds to "free" a lot of memory.
For comparison, using jemalloc instead puts you at 15-25 ns per allocation and per deallocation, with "spikes" that go up to 200ns depending on size and alignment requirements. So we are talking here a 100-1000x improvement, and very often the improvement is larger because these custom allocators are more predictable, smaller, etc. than a general purpose malloc, so you get better branch prediction, less I-cache misses, etc.
I'm a huge Java nerd. I love me some G1/Shenandoah/ZGC/Zing goodness. But once you're writing a program that to the point that you're tuning memory latency in many games anyway, baking in your application's generational hypothesis is pretty easy. Even in Java services you'll often want to pool objects that have odd lifetimes.
Is there a particular codebase you are thinking of here?
We can also look at it from the other direction: if your engine is adjusting its framerate dynamically based on the time it takes to process each frame, and you can do the entire work for a frame in 10ms, does that give you a target of 100 fps? If you tack on another half millisecond to run a GC pause, would your target framerate just be 95 fps?
And what do you do when the set of assets to be displayed isn't deterministic? E.g., an open world game with no loading times, or a game with user-defined assets?
FRAME RATE: having a GC collect at random frames makes for jerky rendering
SPEED: Object pools allow reuse of objects without allocing/deallocing, and can be a cache-aligned array-of-structs. Structs-of-arrays can be used for batch processing large volumes of primitives. https://en.wikipedia.org/wiki/AoS_and_SoA
RELIABILITY: This is probably applicable to the embedded realm too, but if you can't rely on virtual memory (because the console's OS/CPU doesn't support it, or once again you don't want the speed impact) then you need to be sure that allocations always succeed from a fixed pool of memory. Pre-allocated object pools, memory arenas, ring buffers etc. are a few of ways to ensure this.
There's probably a lot more, but those are the reasons that jump out at me.
If you aren't going to use the GC, then you open up a lot of other performance opportunities by just using a language that didn't have one in the first place.
And if you run the GC manually, you really don't know how long it will take - read: determinism.
> you can also write cache-aligned arrays of structs in Go if you want to
Wasn't this thread about why people don't use GC, not about go? I don't remember.
If you're using an object pool, you're dodging garbage collection, as you don't need to deallocate from that pool, you could just maintain a free-list.
> you can allocate a slab and pull from it if you want to. the existence of a GC doesn't preclude these possibilities
To take it further, you could just allocate one large chunk of memory from a garbage collected allocator and use a custom allocator - you can do this with any language. But you're not using the GC then.
The problem is GCs for popular languages are nowhere near this good. People will claim their GC runs in 200us, but it's misleading.
For example, they'll say they have a 200us "stop the world" time, but then individual threads can still be blocked for 10ms+. Or they'll quote an average or median GC time, when what matters is the 99.9th percentile time. If you run GC at every 120 Hz frame then you hit the 99.9th percentile time every minute.
Finally, even if your GC runs in parallel and doesn't block your game threads it still takes an unpredictable amount of CPU time and memory bandwidth while it's running, and can have other costs like write barriers.
julia> @benchmark GC.gc()
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 64.959 ms (100.00% GC)
median time: 66.848 ms (100.00% GC)
mean time: 67.062 ms (100.00% GC)
maximum time: 73.149 ms (100.00% GC)
--------------
samples: 75
evals/sample: 1
Julia's not a language normally used for real time programs (and it is common to work around the GC / avoid allocating), but it is the language I'm most familiar with.Julia's GC is generational; relatively few sweeps will be full. But seeing that 65ms -- more than 300 times slower than 200us -- makes me wonder.
Test was on an i9 7900X.
Does it? In most game I expect resource management to be fairly straightforward, allocation and freeing of resources will mostly be tied to game events which already require explicit code. If you already have code for "this enemy is outside the area and disappears" is it really that much work to add "oh and by the way you might also free the associated resources while you're at it". I don't need a GC thread checking at random intervals "hey let's iterate over an arbitrary portion of memory for an arbitrary amount of time to see if stuff needs dropping yo!".
I realize that I'm quite biased though because I'm fairly staunchly in the "garbage collection is a bad idea that causes more problems than it solves" camp. It's a fairly extremist point of view and probably not the most pragmatic stance.
One place where it might not be quite as trivial would be for instance resource caching in the graphic pipeline. Figuring out when you don't need a certain texture or model anymore. But that often involves APIs such as OpenGL which won't be directly handled by the GC anyway, so you'll still need explicit code to manage that.
That being said I'd still chose (a reasonable subset of) C++ over C for game programming, if only to have access to ergonomic generic containers and RAII.
I write quite a lot of C. When I do I often miss generics, type inference, an alternative to antiquated header files and a few other things. I never miss garbage collection though (because it's a bad idea that causes more problems than it solves).
GC means not only that memory management is simpler, it means that it goes away for most of the programs out there.
Most programmers I know aren't writing code that run Twitter-like servers or avionics, nor are they programming the next Doom game. These people are writing apps and doing back end coding for some big company where real-time isn't an issue and the focus is on getting code out fast, with good average quality and "cheap" labor.
In this case, having a high level language/runtime that doesn't requirs a programmer that can reason about allocations is key.
I can't even begin to tell the kind of codebase that I have seen. Two years ago I was working on a C/C++ legacy system that was thousands of lines of codes and almost every file had memory leaks (that cppcheck could find itself, mind you). Some of them where caused by delivery deadlines, most of them where caused by unskilled employees.
(oh, in case you're wondering: all those leaks where solved by having ten times the hardware and a scheduled restart of the servers)
Even if you are perfect, you will still fragment over time. At some point, you must move a dynamically allocated resource and incur all the complexity that goes with that--effectively ad hoc garbage collection.
There are only two ways around this:
1) You have the ability to do something like a "Loading" screen where you can simply blow away all the previous resources and re-create new ones.
2) Statically allocate all memory up front and never change it.
People went to extreme measures to avoid allocating memory in their games: manually pooling every in-game object & particle, not using string comparisons in C#, etc https://danielilett.com/2019-08-05-unity-tips-1-garbage-coll...
Unity itself finally has a new system they're previewing to average out the GC spikes over time, so a game, say, never drops below 60fps: https://blogs.unity3d.com/2018/11/26/feature-preview-increme...
As well, there is a new way of writing C# code for Unity called ECS that will avoid producing GC sweeps https://docs.unity3d.com/Packages/com.unity.entities@0.1/man...
As for pooling objects, you'd go to those "extreme measures" as a matter of course in any other language as well. You wouldn't want to alloc and free every frame no matter the language.
And allocating memory is fine during runtime when you are in control of the allocation and the cleanup, whereas in Unity, the sweeps are fully out of your control, expensive, and will just sometimes happen in the middle of the action
As others probably already mentioned, worst side of gc is that it is unpredictable and hard to be forced in a way that matches your specific pattern. With manual/raii mm you can make pauses predictable and non-accumulating collection debt and fit before “retrace” perfectly. Also simply relying on automatic storage in time critical paths is usually “can not” in gc envs.
^ if any, I can’t recall if 5.1 actually implemented true incremental gc back then
This is simply not the case. Its still just code after all. The problem was you were fighting the GC but that's just the symptom. The clear problem was leaking something every frame. With all the tooling these days its pretty easy to see exactly what is getting allocated and garbage collected so you know where to focus your efforts.
Not exactly. Here is how the early PC 3D games I worked on did that: They would have a fixed size data buffer initialized for each particular thing you needed a lot of, such as physics info, polygons, path data, in sort of a ring buffer. A game object would have a pointer to each segment of that data it used. If you removed a game object you would mark the segment the game object pointed to as unused. When a new object was created you would just have a manager that would return a pointer to a segment from the buffer that was dirty that the new object would overwrite with data. Memory was initialized at load and remained constant.
One problem with doing things like that is that you would have fixed pool. So there were like 256 possible projectiles in Battlezone(1998) in the world at any time and if something fired 257th an old one just ceased to exist. Particles systems worked that way as well.
What was good about that was that you could perform certain calculations relatively fast because all the data was the same size and inline, so it was easy to optimize. I worked on a recent game in C# and the path finding was actually kind of slow even though the processor the game ran on was probably like 100 times (or more) faster. I understand there are ways to get C# code to create and search through a big data structure as fast as the programmers had to do it in C in the 90's. However it would probably involve creating your own structures rather than using standard libraries, so no one did it like that.
Just have to say, loved that game to bits.
But in practice it can be horrible. You end up writing all kinds of weird code just to avoid allocations in certain situations. And yeah, GC pauses due to having lots of addons is definitely very noticable for players.
Also leads to fun witch hunts on addons using "too much" memory, people consider a few megabytes a lot because they confuse high memory usage with high allocation rate... Our stuff started out as "lightweight" but it grew over the years. We are probably at over 5 MB of memory with Deadly Boss Mods (many WoW players can attest that it's certainly not considered lightweight nowadays, but I did try to keep it low back then). But I think we still do a reasonable job at avoiding allocations and recycling objects...
The point is: I had to spent a lot of time thinking about memory allocations and avoiding them in a system that promised me to handle all that stuff for me. Frame drops are very noticable. But there are languages with better GCs than Lua out there...
Somewhat related: I recently wrote about garbage collectors and latency in network drivers in high-level languages: https://github.com/ixy-languages/ixy-languages Discussed here: https://news.ycombinator.com/item?id=20945819
That's a scenario were short GC pauses matter even more as the hardware usually only buffers a few milliseconds worth of data at high speeds.
Memory management is ultimately far simpler and easier in C++ than in C#.
Bro, game devs talk about this non-stop. There are probably 1000 GDC talks about memory management.
Game devs don't spell the fine details because they are generally talking to other game devs and there is assumed knowledge. Everyone in games knows about memory management and frame budgets.
> If you have a garbage collector that runs in 200us, you could run a GC on every single frame and use less than 3% of your frame budget on the GC pause.
And if pigs could fly the world would be different. ;)
Go has a super fast GC now. It had a trash GC (teehee) for many many years. But Go is not used for game dev client front-ends. If C# / Unity had an ultra fast GC that used only 3% of a frame budget that would be interesting. But Unity has a trash GC that can easily take 10+ milliseconds. (Their incremental GC is still experimental.) It's a problem that literally every Unity dev has to spend considerable resources to manage.
For 50+ years GC has been a poor choice for game dev. Maybe at some point that will change! The status quo is that GC sucks for games. The onus is on GC to prove that it doesn't suck.
Having done both they are remarkably similar. Lots of pooling and pre-allocation. Most GC based languages are a bit harder both because they tend not to give you a chunk of memory you can do whatever with and require a lot of knowledge about which parts will allocate behind your back. It's also harder to achieve because you typically get no say when the collector will run.
There are loads of commercial games that pay no attention to this though written in both kinds of language.
1) Don't allocate or free resources during gameplay. Push it to load time instead. This works for things like assets that don't change much.
2) Use an arena that you reset regularly. This works well for scratch data in things like job systems or renderers.
3) Pick a fixed maximum number of entities to handle, based on hardware capacity, and use an object pool. This works well for things that actually do get created and destroyed during gameplay, on a user-visible timescale.
Together, these get you really far without any latency budget going toward freeing resources. And there is always something else you could put that budget toward instead, which is why any amount of GC is often seen as a waste.
When working on code such as game engines, it's not simply a matter of how long something takes to do, but also when it happens and how predictable it is. If I knew that GC takes 1ms, I could schedule it just after I call swapBuffers and before I begin processing data for the next frame, which isn't a latency critical portion.
The problem is that GC is unpredictable because the mark and sweep can take a long time, and this will prevent you from meeting your 60fps requirement.
In practice, we hardly ever do any kind of GC for games because we don't really need to. We use block memory allocators like Doug Lea's malloc (dlmalloc) in fixed memory pools if we must, but generally, you keep separate regions for things like object descriptors, textures, vertex arrays, etc. There's a ton of data to manage which can't be interleaved together in a generic allocator, so once you've gone that deep, there's really no point in using system malloc.
Malloc itself isn't a problem either, it's quick. It's adding virtual space via sbrk() and friends which can pause you, so we don't. On consoles, we have a fixed memory footprint, and on a bigger system, we pre-allocate buffers which we will never grow, and that's our entire footprint. We then chunk it up into sections for different purposes, short lived objects, long lived objects, etc. Frequently, we never even deallocate objects one by one. A common tactic is to have a per-frame scratch buffer, and once you're done rendering a frame, you simply consider that memory free - boom, you've just freed thousands of things without doing anything.
There are many things you can do instead of generic GC which are far better for games.
I disagree with the author of the original article about C++. C++ is as complex as you want to make it, you don't have to use all the rope it hands you to hang yourself. However, having the std library with data structures, smart pointers, the ability to do RAII, is invaluable to me. Smart usage of std::ref gets you automatic GC, what you don't have is cycle detection, so you take care never to have cycles by using weak pointers where necessary, and you have all the behavior of auto-GC without stop the world.
I second that. I always find the attitude of “C++ is bad so I’m going to stick to C” really bizarre. You can use C++ as a better C.
- use type inference and references instead of pointers. Writing C style code with these features makes it more readable.
- Don’t like OO programming. Stick to struct with all members public. It’s going to be a lot better than doing the same thing in C and you shall not have void* casts.
- use exceptions instead of return code for errors, so that your code is not peppered with if statements at every line you call. With an exception you’ll get additional info about crashes in dumps.
I wrote for an embedded system where the C++ standard library was not available, in a previous life. I ended up writing my code in C++ and “re-inventing” a couple of useful classes like std::string and std::vector. For the most part my code was very C like...
I happen to have the exact opposite opinion. Exception handling tends to feel too "magical" (read: non-deterministic, hard to behaviorally predict, etc.) relative to just returning an error code.
As others have pointed out, this isn't true for many video games. People write things so they're allocated upfront as much as possible and reuse the memory. A lot of old games had fixed addresses for everything as they used all available memory (this allowed for things like Gameshark cheats).
But there's a secondary issue. A GC imposes costs for the memory you never free. It doesn't know the data will never be freed, so it'll periodically check to see if the data is still reachable. Some applications have work arounds for this like using array offsets instead of pointers/references (fewer pointers to follow).
Other issues I've seen include nondeterministic GC sometimes failling to garbage collect one level before we loaded another, OOMing the game. Have you ever forced a GC three times in a row to try and ensure you're not doubling your memory use on a memory constrained platform? I have. (This was exacerbated by cycles between the GCed objects and traditionally refcounted objects - you'd GC, which would run finalizers that would decref, which in turn would unroot some GCed objects allowing them to be collected, which in turn run more finalizers, which would decref more objects, ...)
The last time I encountered a similar (de)alloc spike in a non-GC gamedev system was much longer ago, and it was a particular gameplay system freeing a ton of graphics resources in a single frame, when doing something akin to a scene transition or reskinning of the world - which was easily identified with standard profiling tools, and easily fixed in under a day's work by simply amortizing the cost of freeing the resources over a few frames by delaying deallocs with a queue. More commonly there were memory leaks from refcounted pointer cycles, but those also generally pretty easily identified and fixed with standard memory leak detection tools.
The problem isn't GC per se. It's that most/all off-the-shelf language-intrinsic GCs are opaque, unhookable, uncustomizable, unreplacable black boxes which everything is shoved into. malloc/free? Easily replaced, often hookable. C++'s new/delete? Overloadable, easily replaced, you have the tools to invoke ctors/dtors yourself from your own allocators. Localized GC for your lock-free containers ala crossbeam_epoch? Sure, perfectly fine. Graphics resource managers often end up becoming something similar to GCed systems on steroids, carefully managing when resources are created and freed to avoid latency spikes or collecting things about to be used again soon.
But the GCs built into actual languages? Almost always a pain in the ass.
In other words, the problem is GC, full stop.
UI solutions often/usually use some kind of GCed language for scripting. But the scale and scope of what UIs are dealing with are small enough that we don't see 100ms GC spikes.
Our tools and editors leverage GCed languages a lot - python, C#, lua, you name it - and as long as their logic isn't spilling into the core per-frame per-entity loops, the result is usually tollerable if not outright perfectly fine. We can afford enough RAM for our dev machines that some excess uncollected data isn't a problem.
And with the right devs you can ship a Unity title without GC related hitching. https://docs.unity3d.com/Manual/UnderstandingAutomaticMemory... references GC pauses in the 5-7ms range for heap sizes in the 200KB-1MB range for the iPhone 3 [1]. That's monstrously expensive - 1/3rd of my frame budget in one go at 60fps, when I frequently go after spikes as small as 1ms for optimization when they cause me to miss vsync - but possibly managable, especially if the game is more GPU-bound than CPU-bound. It certainly helps that Unity actually has some decent tools for figuring out what's going on with your GC, and that C# has value types you can use to reduce GC pressure for bulk data much more easily.
[1] Okay, these numbers are pretty clearly well out of date if we're talking about the iPhone 3, so take those numbers with a giant grain of salt, but at the same time they sound waaay more accurate than the <1ms for 18GB numbers I'm hearing elsewhere in the thread, based on more recent personal experience.
GC pause times are relevant but not critical. What's critical is that I can lay out my objects to maximize locality, prefetching, and SIMD compatibility. Look up Array of Structs vs Struct of Arrays for discussions on aspects of this.
This is not strictly incompatible with a GC in theory, however it's common that in GC'd languages this is either very difficult or borderline impossible. The JVM's lack of value types, for example, makes it pretty much game over. Combining a GC with good cache locality and SoA layout is possible, but it doesn't really exist, either. Unity's HPC# is probably the closest, but they banned GC allocations to do it.
Most times it may run in 200 micro-seconds, but the one time it takes 20ms the user suffers from unacceptable stutter.
I can see how people would want to bypass the problem entirely by being a bit more careful up front.
The garbage collector also needs to track resources, which is an additional cost over just freeing them. You have little control over how the memory is allocated, which is an additional cost over a design that intelligently uses different allocation strategies for different types of resources. Then, even if you can control when the garbage collector is invoked, you have little control over what actually gets freed. What good are those 200µs if the stuff eating up memory isn't actually getting freed fast enough?
Maybe people often overestimate their performance needs. A garbage collector may be fast enough for more purposes than anticipated. Even then, managing memory intelligently may seem like a small price to pay compared to the prospect of eventually fighting a garbage collector to get out of a memory bottleneck.
Edit: specifics from https://blog.golang.org/ismmkeynote - Go hugely improved GC latency from 300ms before version 1.5, down to 30ms, then again down to well under 1ms but usually 100-200us for the stop the world pause. So I guess it is stop the world but the pauses seem ridiculously small to me. They guarantee less than 500 microseconds "or report a but", which seems more than fast enough for game framerates (16ms for 60Hz frames). Am I missing something?
GCs really let you trivially not care about a LOT of things... Until you need to care. Things like where and when you allocate, the memory complexity of a function call, etc. With games you need to care about all that stuff so much sooner.
Once you're taking the time to count/track allocations anyway you might as well just do it manually. It just codifies something your thinking about anyway.
https://www.google.com/search?q=memory+management+site%3Agdc...
Basically think about how you can either build a function as recursive and leverage the stack that is built into the programming language or you can write the exact same algorithm in a for loop with a stack object and manipulate the flow of the recursion yourself. they're essentially the exact same thing however one is in complete control of the application writer and the other one is in control of the language. if you use the for lube you can actually say I'm going to run out of memory before you hit the stack limit and actually do something smart it's much harder to do this in a programming language with a built-in recursion. for instance you might approach the stacked limits in the for loop and just returned the current answer.
essentially this analogy goes for the difference between garbage collection in many games and garbage collection in garbage collected languages
Static lifetime management, by contrast, gives the same benefits to memory errors that static typing does to type-mismatch errors: you know, at compile time, how long an object will live and when it will be released. And armed with this knowledge, you can then more effectively profile and optimize.
There are, however, much better ways to go about it than C provides. RAII in C++ and Rust and even ARC in the iOS runtimes allow you to get most of the benefits of automatic memory management while still providing strict, deterministic lifetime guarantees.
This is quite blatantly false. The reality is that every single time your GC pass traverses an object but doesn't free it, it's wasting time doing work that didn't need to be done.
Is the claim that 200us would be the worst case time for the GC to complete its work?
Is this worst case measurement one that the language itself guarantees across all platforms targeted by the game?
If you haven't measured the worst case times, and the system you are using wasn't designed by measuring worst case times, then we're not yet speaking the same language.
https://blogs.unity3d.com/2018/11/26/feature-preview-increme...
I have mad respect for all using low level C / Assembly / Rust in gamedev. But I have a hard time recommending anything other than C# / Unity for secondary school 14-15 year olds just starting their journey. The wealth of resources, tutorials and community online is astounding. And as an entrypoint into VR / AR it's difficult to top ;)
It's common to use stack and pool memory allocators, where this becomes no longer expensive work.
The places where you generally don’t want a GC are with low level engine code, like the renderer - code that might execute hundreds or thousands of times a frame, where things like data locality become paramount. GC does you zero favors there.
The trick is using libraries and techniques that only stack allocate or use your pools. These are techniques you'd almost certainly use without a GCed language but somehow people consider using C before using these techniques is a higher level language.
I think people prefer C and C++ because they can control the memory layout of the app, and specially in console games they can do much more low-level optimisations than using a higher level language. Besides, I'm guessing the pain in C++ doesn't come from memory allocation, you probably follow a well defined ruleset for freeing objects and you know you will be OK.
The problem with gc is that it can blow out a frame's budget, and perhaps extend for more than one frame, taking away precious nanosecs, dropping a 60fps down to 15.
> what they do in lieu of a garbage collector.
Use reference counting. If in the rare case you need circular references then you use a weak pointer, or reclassify your structure so that it's not circular.
That said, where and how you manage the heap can be critical. I recall a few places where I've used the stack itself as a temporary (small) heap, which all gets cleaned up when you exit the function, taking up almost zero clocks to do so.
Err not so fast there. It's quite common in C to allocate an array of objects (not pointers) and reuse them as they expire. Memset is enough to reinitialize them.
And the the main point, lot's of game dev is "C wrapped in C++", game devs tend to rewrite everything for performance and predictability, relying on STL is usually a nope.
Though people who make games sometimes overstate the importance of this. Minecraft is arguably the most successful game of all time despite GC pauses. A competitive shooter like CS:Go, however, can't have that.
The program having a say in how the GC behaves would be one requirement IMO.
> you could run a GC on every single frame and use less than 3% of your frame budget on the GC pause
Could I? In what languages?
the point isn't "Go is good enough", because I don't know if it is because the requirements are not stated particularly clearly. If the GC took zero time, duh, it would work and people could use it, it would save them some error cases and make their programming lives easier. If the GC took 3 seconds, it doesn't work. There exists some latency value under which a GC is fast enough that there's no sane argument for -not- using it. What is that value? That's the question at hand.
Note that those independent cores all share common resources. L3 cache & memory bandwidth are both shared, and a GC workload slams both of those resources pretty heavily. So it's still going to have an impact even though it's running on its own core.
EDIT: Unreal Engine is primarily written in C++, and has garbage collection. I'm not salty about the downvote, but I would like to understand why what I have said is apparently incorrect.
Unreal Engine's GC at least is pretty custom. A lot of effort has been put into it to allow developers to control he worst case run time. It has a system that allows incremental sweeps (and multi-threaded parallel sweeping) and will pause its sweeps as it approaches its allotted time limits.
That said, in my experience with Unreal most large projects choose to turn off the per-frame GC checks and manually control it. For example one project I've seen only invoked the GC when the player dies, a level transition happens or once every 10 minutes (as a fallback).
A few examples: https://handmadehero.org/ https://ourmachinery.com/post/physical-design/
Simple libs widely used in game dev circles: https://github.com/nothings/stb
This one is a full game engine with tools made for educational purpose: https://www.raylib.com/
I do write games and game engine code and tools in C++ without using any of the OOP features.
I know quite a few of other professional, experienced and talented developers doing the same, even full AA studios with up to 50 employees.
Some of them are going back to procedural C after a few years of disappointment with OOP madness.
I'll publish articles about this later, but for now, all I have to say is that it just works.
Newbies are often afraid of manual memory management and pointers, but there are simple ways to avoid shooting yourself in the feet with those.
A cursory look at the CVE list for any C software in the wild indicates that no, there are not simple ways to avoid shooting yourself in the feet with manual memory management in C. It's _incredibly hard_ even for "elite" programmers who have a lot of incentive to avoid these problems. The counterpoint is that people are probably going to spend less time looking for ways to maliciously use those problems in your C game than they are in the Linux kernel, but there is plenty of potential for even seasoned C coders to engage in bullet-on-foot violence.
Writing super secure code is usually not a requirement for gamedev, and I completely agree, this is hard and potentially harder in C than, say, in Rust.
How can I see that list? I'm interested in comparing languages this way.
If by OOP you mean virtual functions? Nobody uses that stuff much anymore, except Java refugees. (They use shared pointers, too.) But, anyplace you would use a function pointer in C, it's cleaner with a virtual function, and often faster.
Why? My understanding is that in modern game dev you're mostly traversing large arenas of structured memory, and you avoid freeing individual objects like the plague.
Excuse my C/C++ ignorance, but why not simply use C?
To avoid cache misses you can't use OOP, that's probably where this wave of going back to C is coming from:
For example here's how someone that knows how computers work writes Java games: http://www.javaonthebrain.com/java/warp/warp.java
C++ with structs and 99% procedural code is actually very close to vanilla C.
Let's not be pedantic here.
I liked Handmadehero for this reason, he is so arrogant and certain about how to properly write code, but when you actually see how he codes, what mess he produces and how long he spends live debugging it all the time. He constantly finds bugs in code he wrote earlier, etc. Yet he is so totally unable to find anything remotely wrong about the way he does things, and that there might have been some ideas invented in the last 30 years that could make him more productive.
I have zero respect for those types of programmers. I would quit any job instantly if I ever had to work with guys like this.
1. Complexity of implementation is not complexity of use. Example: Take std::array vs a plain C array. std::array is a few hundreds of lines of code. But - it's about as inutitive to use; has more convenience features; easier for the compiler to optimize; and doesn't let you should yourself in the foot that easily.
2. If Whiting don't want _any_ of the features of C++ - his software-writing skills are suspect. Now, few people need or want all of C++'s features - but C's are definitely insufficient, especially if you consider both the langauge and some of the standard library (or non-standard common libraries).
3. The complexity you have to tackle with C++ is lower than the complexity of non-standardized often-inconsistent idiosyncratic platforms and libraries for achieving the same things with C (or C + assembly).
So I find Whiting's argument for C over C++ unconvincing. IMHO C++ would be the least-bad language for writing demanding games in.
This is not meant to be a strawman against the complexity cost argument, it's just a related feature of C++ that I thought might be worth explicitly pointing out in case anyone is misinterpreting.
Also, if anyone has a credible dispute against the claim I'd like to hear it.
In game development performance is a feature and anything that makes performance and memory usage less deterministic is frowned upon, like a GC. But iteration times are still paramount and things that make compile, load and link times longer and make it more difficult to debug are also frowned upon. Yes, we'd like to have our cake and eat it too :)
No, that's a different (and orthogonal) concept. The point is that in C++ "you don't pay for what you don't use" - whether it's a cheap or an expensive abstraction, if you don't use it then its overhead won't affect your code's performance.
However, many of the C++ abstractions are zero-cost in many common cases.
> Game developers and low level programmers shy away from them
No, they don't. They shy away from abstractions which are too expensive; or whose scope of optimization doesn't fit the game's use; or which don't combine well with out-of-standard-library code used in the game etc.
But game developers use _lots_ of C++ abstractions; and fancy non-C-like C++ features.
O RLY ? https://gcc.godbolt.org/z/QhbUjI
That goes for most of your points about standard libraries. If you are developing code that you expect (or hope) to run on Windows, Linux, Nintendo consoles, phones, PS4's and Xboxes and expect to know the real performance cost of operations, you will be writing all of your own standard libraries anyway. It is very much a problem of "...the complexity of non-standardized often-inconsistent idiosyncratic platforms and libraries for achieving the same things."
> Complexity of implementation is not complexity of use.
And that's where the language complexity comes into play. Game developers who are bootstrapping need to implement and use these pieces. If you are simplifying your life with C++ features, you must know the ins-and-outs and you cannot rely on new language features because you don't have a choice of compiler in most cases.
> I find looking up language features, and quirky 'clever' api's incredibly tiring. The ideal language would be one I can memorize, and then never have to look things up.
This is a fair ask and is definitely more accurate of C than C++. What you might write is longer, more tedious code, but that is its own trade-off.
"...I care about the speed of the compiler. I am not a zen master of focus, and waiting 10+ seconds is wasteful, yes, but more importantly it breaks my flow. I flick over to Twitter and suddenly 5+ minutes are gone."
No, you absolutely cannot, even in principle. C++ is not "C with classes" and some syntactic sugar like it started out. That's not been the case for many years already.
Also, even the features you can implement - you won't; you don't have the person-years for that. You will have to, need to, use libraries. For those you need to compare the libraries available for C and for C++, and C comes up quite short (not just because C libraries are also usable in C++...)
Which is why it has been done for my 2D Second Life clone?
Some of us can very easily implement C++ stuff in native C.
15 years ago, I spend some time modding with the Quake 3 engine which is written in C too and while I wouldn't categorize myself as 'knowing how to use C effectively' I sympathize with the author's opinion. I totally agree with Go being a revisited C and that C can enable simplicity.
The only part where my opinion is different is about Javascript. Yes, it is a dangerously fast-moving ecosystem, but I think it offers so much value in terms of cross-platform development that I think it is a mistake not to participate. After all, the browser APIs are quite stable and the biggest issue is the constantly changing trend about which libraries are going to be the next big thing.
I want to like rust but everytime I dive in it is getting more and more complicated and verbose.
Rust also becomes a lot less verbose when you get better at it. The ? operator is especially useful.
I prefer to read and write simple C like syntax. I would love it if I can get Rust concept of borrowing in C, or even simple operator overloading..
I am excited about the development of both Zig and Jai since they both are trying to fill what I believe is a much needed role of a C-like language with a few more nice language features like better compile-time code, better compiler error messages, better error handling that doesn't use exceptions, and build scripts written natively in the languages, all while sticking to fast compile times and simplicity.
Games are a decently different domain than others, and there is a lot of valid complaining about C++. It's a known issue, we just need something that's clearly better, which so far I think those two languages are. I've chipped in a few dollars a month to support Zig and have been using it in my free time, but it still very much feels like the version 0.4.0 that it is.
This wait will probably be a few more painful years.
var x:i32 = 5; ugh, it makes no sense.
I disagree - I work at a AAA games studio and we're currently building a little project made entirely in C. Not even a hint of C++. This is probably going to end up being just a little research thing in the end, but don't think that no one does this anymore.
Linux is irredeemably complex, and getting moreso all the time. My code nowadays bypasses it wherever possible -- O_DIRECT files, memory-mapped files, kernel-bypass NIC libraries that set up hardware ring buffers mapped into user address space.
> All my solo project games I've been making recently have been written in 'vanilla' C.
The author is talking about solo projects, not big projects where dozens of teams may be collaborating. It is good that he makes the point at the beginning.
> Nobody does this.
This is just not true. There are solo projects developed in Python, Go, even Commodore 64 assembly. When you do something for fun, you can have as much fun with it as you want.
> It would be nice to make things for the web, but it feels like a terrifyingly fast moving enviroment.
The author should look into Emscripten. It is widely used in the games industry that aims for the web.
> C++ ... It is also slow to compile compared to C.
Probably an important point. You need very good practices to make C++ compile fast. Most people don't follow them as adds complexity to the project.
> Haxe feels much more promising than most alternatives.
Haxe is kind of Object-Oriented and like C++. So, I do not see the point after bashing C++'s OOP so badly. For me, the problem is that it transpiles to another languages. So, when something fails does it in code that you didn't write but the Haxe did. This makes debugging harder that it already is.
> Some people just say screw it, I'll write my own language, the language I want to use. I admire this, and sometimes I toy with the idea of doing the same.
Yep. Something possible for your own fun. Not a good idea when developing commercially. It makes sense for him to choose one language for commercial projects and another, or even create his own, in his spare time.
> It can be made to run on just about anything. Usually this is relatively easy. It is hard to imagine a time when this won't be the case.
This is the big advantage of C. If doesn't run C, probably it just doesn't run at all.
> I absolutely DO NOT mean to say "hey, you should use C too"
For solo projects is a giver that each developer should use the best-suited tools for the task and their abilities and preferences.
I, personally, like C++. I have developed big and small projects in C++ and for me comes natural to write object oriented code. For big projects I see advantatges in being more rigid than C. The extra rigidity makes more difficult that there is unexpected funky stuff happening. So, it is easier to use for teams that maybe situated in different countries. For a solo project, you do not need that rigidity as you know how your own code works.
> This is the big advantage of C. If doesn't run C, probably it just doesn't run at all.
Is that really an advantage of C over C++? They have identical runtime requirements and typically share a common compiler (although one of those, MSVC, barely supports C). Is there actually anything that can run C but which cannot run C++?
You may find that not all C++ features are supported for the least popular processors or for old ones.
Standard C++ threads, for example, were not supported by GCC for Windows for a long time. I do not know what is the current status, thou.
> The author should look into Emscripten. It is widely used in the games industry that aims for the web.
Emscripten is wonderful. If you are slightly careful, a standard C SDL2 OpenGL application can, with a few small changes, run directly in the browser — any modern browser — with WebGL, and your single codebase can target both just by having different targets in your Makefile. I've done it for my own hobby game project. :D
It is a bit constraining as a target. Only WebGL 1.0 is supported by all major browsers, which limits you to OpenGL ES 2.0. And unfortunately, in my experience OpenGL calls in Emscripten (translated to WebGL) are much slower than native ones. But if you're content with not trying to do a max-fidelity AAA experience, it's manageable. Particularly compelling to me is the possibility of letting someone try out your game immediately on your site with no download, or even jump into a multiplayer game with a friend with just a click on a link.
I would reframe this as the design of C++ makes it hard to write code that compiles fast.
On the one hand, it isn't C++, and it has real type safety, and it interfaces with existing C libraries very easily.
On the other hand, it is rapidly getting very complicated, and seems almost eager to become another C++ in terms of sheer volume of language features.
This seems to be the case for almost any "C replacement", and it will be (my prediction, at least) the reason they all fail.
I feel I might be a bit of an outlier in this respect, but I have only ever enjoyed using languages which are small and simple - C, Go, Scheme. The times I've tried Rust, it's been nice to have code that cannot segfault, but I find it such a chore to deal with. Whenever I've read open-source projects in Rust it's always a readability disaster. Also the compiler emitted warnings because functions were not snake case, which I found obnoxious.
Protocol-oriented programming is extremely useful for games and I like Objective-C for that reason. (Objective-C doesn’t meet his mentioned goal of portability but this is an example where C++ could be used to gain “light” inheritance without having to opt in to the rest of the mess that C++ can be.)
Clang implements it, last I checked, meaning that it should be (at least in theory) portable to every platform Clang supports, no?
The article mentions that:
>> What I want from a language
>> The strongest thing on my desired, but not required list is simplicity. I find looking up language features, and quirky 'clever' api's incredibly tiring. The ideal language would be one I can memorize, and then never have to look things up. (emphasis mine)
The goal (at least mine and of some authors following this minority trend) is to write and read simpler, more straightforward code.
Take a look at the code in the examples of the following url to see what I mean.
Not really... Not only the generated assembly code heavily depends on the compiler (LLVM, gcc, Intel, MSVC...) and the optimization settings, you cannot be completely sure what the machine code generated by the assembler even looks like these days.
If the language is so small I never have to look up anything when using it, it's also so small I'm going to have to spend ages re-re-re-implementing basic functionality that I get for free from a higher level language.
> I am not an OOP convert. I've spent most of my professional life working with classes and objects, but the more time I spend, the less I understand why you'd want to combine code and data so rigidly. I want to handle data as data and write the code that best fits a particular situation.
I've come around to the idea much more that data should be the privileged structure and code should be considered transformations of the data. In some sense, code should be light and interchangeable and let the data is the first class citizen.
Are OOP and "data-centric" approaches at odds with each other? Does OOP only have validity when the data is relatively "small" and can be abstracted away easily?
Yes, because OOP Objects are data plus code (methods). If you don't have methods, but just pass data around, you don't have OOP. If you do have methods, you meet the "expression problem".
If you have static types and well typed code you get pure data at runtime, and if you include unions and product or sum types at runtime, you end up with algebraic datatypes, and if you have RTTI and polymorphism and inheritance then you get classical OOP.
If you have just data living in memory at runtime, and just transformations of the data (functions) sitting in your code, then you get FP.
You could easily treat Unity as a cross platform rendering engine and asset loading system, and then build your own engine on top of it. I see very little reason to drop down to C or C++ at this point, especially if your goal is to ship your game to as many people as possible.
That said, I'd love to see a pure C# engine on .NET core 3 that has access to Span<T>. C# is set up as a great high level language with low level features as long as you're willing to do the work you would have done in C or C++.
I've built a non-trivial engine active from around 2010 to 2015 that targeted all iOS and Android devices. Our test fleet of devices was enormous because we'd run into silly issues that were always device specific. My favorite, one particular phone with one particular driver version for the GPU did not correctly implement the ES 2.0 standard for constant defined variables. It caused our games to crash, and because of the scale of mobile this affected hundreds of thousands of users.
At this point you could not pay me to build an engine, because for anything that isn't a toy you end up having to solve these problems yourself.
Treating Unity as a rendering engine and asset loading system is not immediately "easy", but it's a very far cry from "hard". Dismissing it or other options simply because of unfamiliarity or a shallow distaste for OOP is ignorant at best and actively misleading otherwise.
As far as data types go, I haven't used much more than linked lists and basic hash maps so far. For everything else I use fixed-sized arrays. It helps you precisely control how much memory the program is using without worrying that some higher-level magic garbage collector will overflow. Despite having that level of control, I haven't been frustrated by it yet. Yes, the standard library is very basic, but you can add the types you need very easily. There's some beautiful C code out there that will give you exactly what you want without having to include hundreds of obscure futures you'll never need. Overall, I've been very happy with it, and I look forwards to trying to run my code on all kinds of weird and wonderful platforms. I see huge competitive benefits for C code bases, to be honest.
Also there are many kind of games that do not require a real time user interaction including many turn based games, card games, etc, so the type of game could be a factor in the selection of the tool. Some time s I guess game design can incorporate pauses and delays that might be required for technical reasons.
things like:
(Composing structs)
struct Parent {int number}
struct Child {Parent; int another_number} // anonymous struct convention
Child c;
c.number = 10;
c.another_number = 11;
(Object notation, namespacing) int DoThis(Child* this, int input) {}
Child* c ...
c->DoThis(10)
(Someway to define public/private in structs and methods, like Go do with convention of Lowercase/Uppercase components/function names)and Finally
(Someway to define a interface and use vtables when needed just like Go´s Interface but in a more C compatible way like.. )
struct MyInterface {
int (*Read)(int a, int b);
int (*Write)(int a, int b);
}
or struct MyInterface {
int Read(int);
int Write(char);
}
struct X {}
int Read(X* this, int n) {}
int Write(X* this, char c) {}
X x = X{};
x.Read(10);
x.Write('b');
MyInterface* c = &x;
(...)
(templates) - C++ way is fineThats it. With all this you would have "a better C", with compability with C codebase and still a language much simpler than C++, yet powerful.
Which IMHO basically amounts to trolling for HN comments if you post it here. Now people are just going to argue how wrong he is, against and for their favourite languages and nobody will learn anything new or useful.
Is this guy well-known or something? Did he make any good games in C?
Maybe I'm missing the point of this article (someone please explain), but it looks like it's worth as a post is only as a discussion starter and an inflammatory one at that. If there was any time a reason to flag a discussion thread for being useless, it's this one I think.
The core interfaces are really not terrible at all. They do have danger zones, but their complexities are relatively low. As the author of this article suggests, it's within the realm of something we can remember. That's a BIG deal.
I haven't done C in a very long time. But with C, my recollection is that the biggest issue is to make sure you keep track of your memory allocation and releasing. Sure this is significant, but it's a direct concern. And if you abstract by one level your management of memory, it's a lot easier (especially if you include some testing).
I think it's important to periodically evaluate if our efforts to simplify a situation have actually alleviated or simplified real issues that were hurting us.
The Unreal Engine is around 4 million lines without dependencies (e.g. PhysX, a proper audio engine, etc). You could try to do all that in C (good luck finding a good physics library with a plain C interface, btw.). But you need to have proper software design throughout the project and you will likely end up replicating some kind of inheritance or polymorphism scheme somewhere.
The Unreal Engine is far more than a modern game. You wouldn't call Clang and editor just because Vi was compiled with it.
And there's still plenty of life left in 2D anyway. Not everyone wants to crank out the next Fortnite or whatever the kids are into these days.
Civilization.
Pretty much the only things I miss in C for gamedev are sensible string manipulation and lambdas/closures. Otherwise it works really well - I don't think I would be able to make my games so multiplatform (at the moment GNU/Linux, macOS, Windows, Android, Emscripten, Librem 5, Nokia N900, Pocket CHIP, Raspberry Pi, Steam Link, Nintendo Switch w/ devkitPro; more to come...) so easily with any other language.
What C only compiler were you thinking of? Most of the ones I can think of are toy compilers. If you're making a game, you're going to be using clang/MSVC/gcc.
For catching bugs early, at least as important as the compiler are
* Enabling additional compiler warnings and -Werror
* Checking for memory leaks with valgrind
* checking for Undefined Behavior with UBSan
* Using mix of static code analysis tools such as Coverity, PVS-Studio, and the Clang Static AnalyzerThis is mostly down to a lot of design decisions in Go made with the goal of speeding up compilation. Elimination of header files, pre-compilation of Go modules, efficiently parseable syntax and more.
From what I've seen, first-time compilation of Go programs competes well with C, while incremental compiles are practically instantaneous.
All support for OOP would vanish overnight if people realized you can do all the important OOP stuff using C structs.
This makes no sense to me.
By that logic, it would be difficult for me to create a scheduling system for classes being taught in factories with OOP, which it's not.
I didn't mean to say it's an earth-shattering obstacle. Just one minor additional nitpick to add to the gigantic pile of problems caused by OOP.
Am I an idiot for not knowing for sure this is a joke, or is the poster an idiot for not intending a joke?
I'm so confused...
Imagine you had a program involving, say, chairs. It has things called chairs, which you pass around to functions which take chair arguments. Suddenly, the next version of the language comes out and now "chair" is a reserved keyword, and all your code is broken. To fix it, you have to go through your whole codebase and rename all your "chair"s to "ChairStruct" or "_chair" or something.
I didn't say it was a major world-stopping difficulty. It's just one additional nitpick against OOP for games (added to an already gigantic stack of much bigger problems caused by OOP).
Such as having a safe, leak-proof string type? I think not.
Haxe has actually been around since 2005 or so. I'm really liking Haxe as a general-purpose compile-to-just-about-anything language.
Indeed. Clever apis are a disease endemic to the JavaScript community. expect(me).to.puke.when(i.see.shit.like.this());
> The ideal language would be one I can memorize, and then never have to look things up.
You can't memorize C. The idea that you can "hold C in your head" is a myth, and propaganda repeated by C pushers. In reality, the gotchas surrounding undefined behavior are so numerous and subtle that you're much better off using something else, even if it means accepting the complexity that is C++. (In reality, what you want is a language such as Rust that has well-defined behavior.)
> I need a platform that I am confident will be around for a while.
Don't get your hopes up. The closest we've had is POSIX, but POSIX is showing its age, and in the modern era churn is the rule, rather than the exception. More than a decade old? Rewrite it! New framework came out? Rewrite it! Developer fell in love with some flashy new technique? Rewrite it! Not enough nonbinary PoCs on the dev team? Rewrite it!
Rewriting software sucks. We usually don't like solving the same problem more than once.
Previous discussion: https://news.ycombinator.com/item?id=10870488
To me the STL containers are good enough. It's always a balance between how much time you save, and how much performance you lose. C++ is good enough with this. The only lacking thing would be an object pool.
The author writes
> but so simple it's not too hard to learn to use it carefully.
I would really love a replacement to C++, or an subset of C, that have the best of both languages. I can't use C because it becomes hairy very quickly, since you don't have things like STL containers.
I don't like Rust for the exact reason I quoted the author. The learning curve of Rust is too step, and its syntax if to distant from C. C is great because it's easy and fast, but it's also lacking many modern features that makes the programmer's life easier, without stomping down on performance or customization.
It's true that C++ is very complex, but I still believes it's very very usable, and the standard is improving a lot.
I think Rust has a different lane: Rust is for when safety is absolutely important. C is for when you want very little abstraction between you and the hardware, and you just want it to do what you tell it.
Some day, C will be deposed. Something better will show up and gain traction. On that day, I will happily switch to the new better thing; but for now, C meets my requirements pretty well.
FreePascal! :-D
> Can D be used to make games? Yes. Has it been used in a major game release? It has now. Remedy Entertainment have successfully shipped the first AAA game to use D code. And it’s in a fairly critical subsystem too. This talk will cover the usage of D in Quantum Break, problems encountered and solved, and where we want to take our usage of D in the future.
He doesn't want to combine code and data rigidly, so he uses a language without any concept of generics.
I think he doth protest too much, he can write code however he feels like, and Im not going to deny his personal opinions, but the justifications and reasons for those opinions are all just uniformly nonsense.
Certain things are very nice about C++ as compared to C, especially C++11 on:
* type safety and expressiveness (aided by judicious use of templates)
* The constexpr keyword for real compile-time constants (again, type safety)
* RAII (often aided by statically allocated pools of memory from which one can then allocate objects from at runtime)
What kind of issues with code size have you had (and using which compiler)?
I use Keil, IAR, Renesas's compiler, XM8/16/32 from Microchip, and GCC.
Which do you use?
One of the main features of C++, its object-oriented stuff, is built on top of constructors and destructors -- aka dynamic memory allocation. This sort of thing is not desirable on deeply embedded systems due to resource constraints and the very real spectre of heap fragmentation -- embedded stuff tends to rely on static memory allocation done at compile time or system startup.
C++ isn't quite as portable as C -- Not just the compilers which never quite supported the same combinations of features -- even if you just stuck with the ISO-ratified features -- but the language runtime, standard [template] library, and headers are far larger with various implementations including plenty of mis-features and quirks. All of this meant that it was quite common for C++ code to not even compile with a different toolchain -- especially when templates were used -- and even if it did compile, it might not actually function correctly.
If you restricted yourself to avoid the problematic areas of C++ (eg not using templates or dynamic allocations) you'll end up with a language that is basically C. So why not just use C? :)
OTOH Arduino is C++ and works fine for millions of people, I've also dabbled a bit in various AVR, STM32 and ESP8266, all in C++.
I used to write games in plain C, and nowadays I'd definitely use Rust for them.
There is a small Rust games community and a couple of engines/frameworks: https://lib.rs/game-engines
One thing with Rust is that it pretty much requires use of entity-component-system. It's the best practice for real-world games anyway, but people who write their first game are surprised they can't just "wing it" with some ad-hoc OOP.
Writing C code is also writing C++ code, but badly.
Unfortunately OCaml is single-threaded, has a stop the world GC (though if it’s good enough for Jane Street it’s probably good enough for you), and sometimes has unfortunate syntax (though ReasonML fixes a lot of this). Also OCaml doesn’t have the greatest type system in the world. No higher kinder types and instead we get the parameterized “functor” module system. Which honestly, kind of sucks. A lot of OCaml is an artifact from the past, if it could be remade today it would really be something great. Even so, OCaml is a great language and doesn’t suffer from a lot of the dogma that Haskell succumbs to.
I’ve always thought of and used OCaml as the functional programming equivalent of C. No unnecessary bullshit, just straight coding.
A non CLOS heavy SBCL codebase would also be excellent.
"Safety. Yes, Rust is more safe. I don’t really care. In light of all of these problems, I’ll take my segfaults and buffer overflows. I especially refuse to “rewrite it in Rust” - because no matter what, rewriting an entire program from scratch is always going to introduce more bugs than maintaining the C program ever would. I don’t care what language you rewrite it in."
From https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-repl...
(I think it's a bit mean to downvote me for not knowing that he addressed that point in a separate page of his. He didn't link to this Rust review at all!)
Every console will have a C toolchain out of the box, we can't say the same for rust.
”Similarly I want to avoid tying myself to a particular OS, and ideally I'd like to have the option of developing for consoles. So it's important that my programming language is portable, and that it has good portable library support”
Availability of Rust across platforms is nowhere near C or C++. It may, and I hope it will, change in the future but right now it is not so.
Yeah, at strict typing you lost me buddy. Let's just go with somebody else reply, as in you like C and that's why you do your hobbies in. Nothing wrong with that in the end.
When you go further, you can see that the core innovation of Rust is basically adding ownership to the type system and therefore making typing even stronger. Strong typing is a win for less bugs. Full stop.
Your TypeScript versus JavaScript comparison is about as good as you can get. Many people I've met who think duck typing is the preferred way to go do so because they learned to program on JavaScript. That is more familiar to them, TypeScript is an extra burden to getting work done, and therefore is bad.
I am working on a large project that uses TS (Angular) on the front-end and I can't possibly imagine doing that in plain JavaScript. The lost hours to simple, avoidable bugs would be too great.
Definitely another reason to stick to C. In C you don't have to change to another language or another framework, or yet another design principle, or whatever hype that is being followed by a horde of idiots that think they're incredibly smart.
C is still C. I love that so much. No endless discussions about type safety. And yes, with C I can shoot myself in the foot, which is great because I like sharp tools that can cut. Tell me of a cook who prefers a blunt knife. But at least, I'm the one doing it in contrast to web development where you use hundreds of amateur libraries that kill you in a snap, without you ever finding out what actually happened.
Cliche analogy.
Most of the rest of us have to learn and use a wide swath of languages, features, and technologies to stay a leg up in this world. We seldom have the choice to pick what we work on. Sure, we can change jobs, but inevitably it requires conforming to someone else’s opinion.
The author makes me want to go back and pick C up again. But like so many times in the past, I will probably find it needlessly painful and go right back to the more modern languages which already solved so much for me. I don’t mind standing on the shoulders of past C developers. I, for one, also do not mind learning the details of any particular modern language because, if anything, it makes me more marketable.