Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Very nice code quality.

C gets a hard time and this code base highlights some of the modern features C is missing by its use of perfectly clear workarounds. E.g. The prefix “namespacing” done here works just fine, easy to understand. The go-style visibility approach is growing on me lately and it works well here.

C does deserve some of the criticism right enough, e.g. the same macros re-defined in a few places - it’d be nice if c had a better way than its version of macros although its not actually a problem here because the macros aren’t really big enough to have subtle sharp edges.

Ahh a beautifully written project, just shows that maybe C is entirely workable in the right hands.



I see a lot of salt in the comments below on the C language. Like, "how do you scan a string", "it is unteachable to junior devs", "not because you are used to it, it does not mean it is any good", etc.

Well, the same old critiscisms again and again. You can do shit in any language if you don't understand what you do. For instance, Duck typing in Python or Javascript can lead to huge mistakes at runtime, code injection and unchecked values in java can lead to severe runtime crashes, etc.

So yeah basically you want good code quality (knowing what you do) and good unit testing in any language.

C has proved totally workable on many occasions. I guess a lot of the comments on this thread where written from a Linux Kernel :)


Or a Windows Kernel which is also (mostly) C.


The nice thing about C is that it's so small that you can hold every corner, nook and cranny of the language in your head. There must be less than a dozen people in the world that can say that about C++.


Quick, I need to parse a simple string; should I use scanf, fscanf, sscanf, scanf_s, fscanf_s, or sscanf_s?


You write your own parser.

Seriously, I don't use scanf that much in C. Most of the times, it doesn't do exactly what I want, or there is a more specialized alternative (ex: strtol). Also, generally, %s is bad, even the "safe" variant. For sscanf, it makes a copy, which is inefficient (if you are writing C, you want efficient code, right?) and you have to know the size in advance, and usually, you don't.

Most of my string manipulation in C involves loops, pointers and a lot of in-place manipulation. It is tedious compared to other languages but it is very efficient, the reason I picked C in the first place.


So in the context of the larger debate, this answer falls in to the "C is simple, until you try to use it, at which point you start writing your own programming language in C to write your application in," category. In this perspective C is simple mainly because it's missing the already-written code that you have to benefit you in other languages.


I don't think any of this is close to fair. "C is simple" and "it takes some work to do stuff in C sometimes" aren't mutually exclusive, in fact the former necessarily implies the latter. C is simple, which means it doesn't have a pre-written obscure built-in or convoluted syntax to handle every possible thing you might want to do in the language. You're going to have to write some of the code in your project yourself. That doesn't mean C isn't simple. I don't think I have to get into why writing a string-parsing function isn't quite the same thing as writing your own programming language.

And C has libraries too, to be fair. So if you prefer the "choose between 25 competing libraries that all provide the desired functionality but half of them are deprecated or don't work" workflow a lot of people are used to from C++ or whatever you can do that. Lots of people use libraries in C. In fact, as you might be aware, your OS's package manager doubles as the C language's third-party package manager. There has been plenty of "already-written C code," because it's a language that has been ubiquitous in computing for fifty years.

I'm not sure where these same tired critiques bubble up from but I see them constantly and they don't make sense. There are a number of entirely valid and damning critiques to be made of the C ecosystem but "there aren't any libraries written in C" and "oh, so you say C is simple, huh? Have you tried using it?!" aren't among them.

You're not going to be a 20 year veteran C developer and then suddenly just today learn about some obscure built-in, and that's what is meant by "simple" here, and that's a good thing.


>You're not going to be a 20 year veteran C developer and then suddenly just today learn about some obscure built-in, and that's what is meant by "simple" here, and that's a good thing.

But you could suddenly learn about a new library feature that performs the same function as another language's builtin, which is for all practical purposes an equivalent experience. In both cases you see an unfamiliar string of characters in someone else's code, and then look up what it does.


someone else on the internet writing a function does not make the c programming language more complicated. c's (lack of) library namespacing, which is responsible for this confusion, is, frankly, one of those valid critiques, but this is gibberish.


I don't understand C programmers are their seemingly utter inability to understand that C is actually not perfect and has tons of flaws, including a lot of complexity. Just because you're used to something doesn't make that thing not complex. Try teaching C to first year undergrads.


I don't understand non-C programmers and their seeming utter inability to understand that C programmers are very aware of C imperfections and how to effectively deal with it, while being equally blind to the flaws and complexity in their own environments.

I learned C as a first year undergrad.


I'm a final year undergrad and all my courses have used C except for the funcprog and OOP courses which used haskell and Java respectively.

I've even been a teaching assistant for the very first programming course students take, so I have taught C to first year undergrads!


I feel like undergrads learn C in their first year? Or at least I did (well I already knew it, but had a class that was in C).


I don't think C is the standard anymore. I'm graduating class of 2022 and my college taught Java to freshmen. My friends at different colleges mostly had the same experience, with a few using Python or C++


This definitely varies by university. I had haskell and C on my first year, only had Java on the second.


On my university we learned C++, already with proper strings, vectors, in-house collection library.

About 30 years ago.


C the language is really neat and exciting. C the ecosystem, including standard libraries, is a lot less compelling.


I do (well to apprentices who are the same age and same level) I teach about 2 hours a week on c++ and they are fine. They pass the cpp institute exams (which are super hard!)

So unless you actually DO teach then don't assume :)


Have taught first year undergrads. Highly recommended experience: 5 stars.

C definitely has many flaws, but I wouldn't say complexity is one of them. I wulld rather say it's too simple and one has to add the complexity all by themselves :D


Learned C in a class as a first year undergrad, it was great!


C was my first language as a first year undergrad, in mechanical engineering to boot. I think I came out of the experience just fine.


How often do you need to parse a string in an RTS game?

If you need to do lots of string processing, C is simply not the right language.

Thankfully one isn't forced to only learn a single programming language in life ;)


>How often do you need to parse a string in an RTS game?

Asset files, config files, shaders, obj files, CSV, XML, JSON, etc... it's not uncommon. Read the asset loading code in the posted game.

For a language that doesn't even know what a "string" is, C applications tend to involve a lot of string parsing.


I type "man scanf" and check which one I need, I wish other languages had such accessible documentation.


Is this supposed to be a trick question? All do virtually the same thing at their core, differing in where they take their input from and whether they are the C11 bounds checking variants. You can add all the v prefix variants too, doesn't really make things more difficult.

You could have thrown in a bunch of functions from string(3), but even then... C string handling isn't great by any means, but it isn't vast or complicated either.


You can learn the C stdlib+system headers, but at that point you're doing something more or less equivalent to learning a high level language's features, and you're past the simplicity of K&R.


I'm not sure what you mean. C is a high level language. And yes it and standard library has obviously grown more complex than it was 50 years ago.

It's small and easy to grasp the entire thing though, unlike most other more modern high level languages.

That said, it's not entirely clear that matters too much. Almost any non-toy C project is going to be bringing in libraries outside libc, so you still need to go off and learn those if you want to work on a project. Doesn't really matter whether they're in the base language or not.


I think the argument is converging on, "by the time you do anything useful with it, C is as complex as any other language, except for C++ which almost nothing is as complex as." That's more or less what everyone seems to agree on.


Ready for a random question out of the 200+ UB cases on the ISO C, or about a random feature from any C compiler?


I'm not sure if you replied to the wrong comment or not because it doesn't address what I wrote.


I surely replied to the right comment,

> It's small and easy to grasp the entire thing though, unlike most other more modern high level languages.

So ready for the question?


> I surely replied to the right comment,

Oh, well it didn't address what I wrote.

> So ready for the question?

Doesn't address what I wrote.


> It's small and easy to grasp the entire thing though,

Whatever


If you asked some dumb question and I answered it, it wouldn't change your mind would it? If it did, then your position is so flimsy it's laughable. Just make a point in plain language rather than rhetorical questions that vaguely relate to the comment but are lazy incomplete thoughts that don't actually address what was written.

You would have a point about knowing exactly every single corner case of the language. I don't think that's easy even for C. Fortunately I didn't claim it was either.

> grasp


> You would have a point about knowing exactly every single corner case of the language. I don't think that's easy even for C. Fortunately I didn't claim it was either.

You said "It's small and easy to grasp the entire thing though, unlike most other more modern high level languages."

The entire thing doesn't include corners? And you entered the conversation in support of someone that literally said "every corner, nook and cranny"! If you were making a much weaker claim, it's on you to make that clear. Instead of this petty "doesn't address what I wrote" garbage.

On top of that, being able to suss out what is undefined behavior is in fact quite important for proper use of C. I wish it was a super rare corner case but it's not.


More half cocked rhetorical questions.

> The entire thing doesn't include corners?

It does.

> And you entered the conversation in support of someone that literally said "every corner, nook and cranny"!

So reply to that.

> If you were making a much weaker claim, it's on you to make that clear.

I did. You just didn't read what I wrote.

> Instead of this petty "doesn't address what I wrote" garbage.

Garbage. Ironic.

> On top of that, being able to suss out what is undefined behavior is in fact quite important for proper use of C. I wish it was a super rare corner case but it's not.

Sure. Doesn't change the fact your question was stupid and didn't address what I wrote.


In general though c++ has some level of feature orthogonality, that is features you don't use will not hurt you. But for large projects, C++ is the saner choice with its builtin library of data structures, templating, namespaces etc..

All these could perhaps be done in c, but why spent a lot of time making this infrastructure if one could spent more time on ones application domain solutions.


You gloss over the complexity cost of having too many overlapping tools in the bag though, a complexity cost that has drowned projects in the past. Arguably C++ as a language could be improved if someone was able to depreciate and remove the "legacy" parts.

In practice though that's simply not possible. E.g. trivial example but you couldn't ever depreciate #include even though we have #import now.

Instead of "fixing" the language, there's written (not enforced by the compiler) documentation https://github.com/isocpp/CppCoreGuidelines - actually the first line is the perfect summary:

"Within C++ is a smaller, simpler, safer language struggling to get out." -- Bjarne Stroustrup

This is great documentation (and many parts are relevant to other languages) but many of its proscriptions cannot be enforced by the compiler so it will always be opt in, mis interpreted or ignored.


I count Stroustrup, Walter Bright... and 10 posers


Stroustrup once ranked himself a 6 on a scale of 1 to 10 as to how well he knows C++.


I've been known to rank myself as a 6 out of 10 "where 10 is Stroustrup".


I haven’t written C professionally or hobbily in 5+ years and I can fairly quickly get accommodated in open source projects. I know that it has many valid criticisms but C is such a pleasure.


If a project requires an open API like OpenGL, SDL, Vulkan which is documented and defined using ANSI C or C99 language constructs, then some amount of C is required regardless of the merits of the language. With high level language with large community maybe someone writes and maintains wrapper, library, or FFI binding for you but this relies on economy of scale and out-sourcing work. If a programmer doesn't understand a C API then writing a binding to a function they don't understand in order to test it is a point of friction which encourages returning to plain C. Ideally more of the "Better C" languages would retain ability to parse and compile vanilla C header files without manual binding. Zig attempts this but reduces the cognitive simplicity of the language by using structs as namespaces for holding local function definitions rather than as "plain old data".


> Ideally more of the "Better C" languages would retain ability to parse and compile vanilla C header files without manual binding.

I've been doing a lot of zero-copy FFI work in Haskell. It became quite mechanical to create 100% literal bindings to C libs (Pointers and all), and it's nice to use it directly in Haskell as-is. I'm hoping I can write a simple tool to do exactly what you describe.


Indeed. For a language that strives to be as different as Haskell does, the FFI experience is absolutely stellar. The only other language I've seen come close in terms of C interoperability is perhaps Rust.


You should take a look at Zig.


For programmers which need to consume C APIs ideally the tool is the standard compiler. Eeverything gets handled at compile time without the need for community-maintained cache of bindings. New language designers start with a C compiler which handles header files, then extend compiler to also parse NewLang files. The in-memory representation is loaded from both source types, without throwing away the C functionality. In NewLang there is some standard namespace and calling convention.

    @importc "stdio.h"
    num : I32 : 123456789
    fd : c:int : c:open(c:"file.txt", c:O_RDONLY)
    :: c:print("file:%d  number:%d\n", fd, num)
But nothing more, everything else is handled by compiler.


For a "Better C" language, that would be wonderful.

For my Haskell ideas, generating Haskell from headers automatically feels like the best route. Especially if it's configurable to some degree. It's common that you can better type a C API with Haskell than C ever could. Usually with techniques like IO, phantom types, GADTs, and the new linear types.


The chicken-and-egg problem is where programmers don't know how the C API works well enough to provide function-specific type annotation or library-specific configuration settings, but want to try calling the API anyway because they are following a tutorial written in C or C++. In this situation if there is no "dumb" binding mode builtin to the language the programmer is likely to abandon the language and go back to writing C or C++ until they get the IO working and something displayed on screen.


The last paragraph sounds so no weird to people who have worked years of their life in firmware development.

Of course it's workable. It does have a lot of gotchas and seems to foment a propensity to reinvent the wheel (Although that also relates to the limited contexts in which it is used).

I'd generally avoid it unless it really is the right tool, in which case, just approach it in a methodical way, like every other project.


What I think is the greatest problem of C is the confusing syntax for types. Other than that, I think C is good (and in many ways, does many things better than a lot of modern programming languages do), although there are things I would improve (some of which are already implemented in GCC), including a few additional macro functionalities (including adding to existing macros like {append} does in Free Hero Mesh, and a kind of macros which are scoped as variables similar to vardef in METAFONT, and the ability to count the number of arguments to a macro (without expanding any of the arguments), etc)


> Ahh a beautifully written project, just shows that maybe C is entirely workable in the right hands.

And because it is in C, you can talk to it from Nim, Python, Rust, etc.

And scripting is a very big part of RTS games.


> And because it is in C

I'm curious, what difference does it make if it was written in, say, Rust, or C++. Wouldn't you still be able to talk to it in a similar way? What's so special about C in this context?


The thing that's always special about C, it's basically the default goto programming language for calling from higher level code, most languages that support FFI do so by being able to communicate with C apis and having tools written around C. C also lacks more complex things like classes, it's much easier to build up a complex piece of software from C building blocks bound to your language. Think of like a struct that has a set of functions that operate on it. You can map that basically directly to a class definition in most higher level languages and then wrap up the c implementation details like memory management behind the scenes so you can produce a nice clean api in your chosen language to use.

This practice is fairly common in python when you're dealing with AI (I'm sure there's other examples, but this is the one that sticks out to me.) SDKs like TensorFlow are written in a lower level language (C++ I think for TF) and then FFI called from python, so you can end up with a function like initTensorFlow() and calling it is enough to bring TF online and ready to start processing other data.

And you may be thinking "Well, I can do all that in rust or go or <insert prefered language here>" and of course you can, ultimately it's all just a linker and compiler deciding where to put code and data in an executable, but C is the defacto standard and more tools from more languages are going to work well with it than say, rust or go. Which, now that I think about it, is probably why Rust supports defining a c api for your code, so it's easier to call from other platforms. Rust specifically tries to play nice with being embedded in existing projects because it makes it easier to adopt rust incrementally.

Just my $0.02


"The thing that's always special about the C ABI..." Fixed. The C ABI is not in anyway restricted to the C language.


An important distinction. Thank you.


Nowadays, 40 years ago it was hardly the case unless one worked at a place rich enough to afford UNIX workstations.


You can expose a C ABI from Rust, and some folks do, but it takes some work. Some people don’t find that acceptable.


To me, If it solves the problem it’s acceptable.


C constructs normally map 1:1 in most languages. It's rare for a modern language to not be able to do all the things that C does. And, even if it's a little weird, it's not too hard to give out an escape hatch because the features aren't that advanced.

C++ constructs--not so much.

Does your language allow overloaded functions? If not, okay, you have to map that somehow (probably name mangling).

Oh, your language has a different notion of inheritance than C++, well, now you need to map vtables somehow.

And what does your system do about exceptions? Constructors/Destructors? Does it agree with the standard library about what a String is? What about memory allocation? etc.

Even if the C++ ABI was perfectly described and standardized, mapping to it is always going to be error prone and clunky relative to the C ABI (which is just a lot simpler).

If you want a good example, look at what it takes to use Vulkan via the C API/ABI--which is an exemplary implementation of an API, in my opinion. Look at the hoops you go through with loaders, structs that have type fields in them, extension fields so that you can add functionality without breaking compatibility, etc. All of that extra "gunk" is the kind of thing that C++ hides normally that gets terribly exposed when you have to cross an ABI boundary.


> It's rare for a modern language to not be able to do all the things that C does.

I agree with the general gist of this, but:

• Java has no unions

• Java has no rectangular multidimensional arrays, although of course C arrays decay to pointers when passed anyway

• Java has no unsigned integer types, I imagine this is generally easily handled though

• Java has no const modifier, you'd need to keep track of that manually

• Java has no preprocessor, although of course this happens at the API level not the ABI level

• Java lacks bitfields but they're generally avoided in C APIs anyway, for good reason

• Probably worst of all, Java references are importantly different from C pointers

You're absolutely right though that the C ABI is a pretty good lingua franca ABI largely due to C being a pretty minimal language. No overloading, templates, garbage collector, or object model to worry about.


I think, as a generalization, most communication between languages goes through C. For example Rust or Switft to C++ would go Rust -> C -> C++. Or Switft -> C -> Rust as examples. I think Swift is trying to make C++ bindings, but in general most languages can talk to/from C.

Of course there are other mechanisms, but for direct bindings I think C bindings are by far the most common.


Actually for Swift <-> C++ you tend to go via Objective C++, which as easy as renaming your .cpp file to .mm, and calling in to Swift via a bridging header


The important part is "C API", the actual implementation language under the API isn't all that important, but C is usually a good fit as implementation language for a C API ;)


Can you elaborate on the “go-style” approach, or link an article or two?


If you look at the source code, "public" functions are capitalized, and "private" ones aren't, by convention.


What’s the reason for serializing the full engine state for the save/load functionality? Seems like a lot of complexity.


I guess because RTS games are soft-realtime systems and need precise control of everything. RTS multiplayer logic is some of the most complex I've ever seen, these games are basically distributed computing frameworks.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: