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 :)
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.
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.
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.
I learned C as a first year undergrad.
I've even been a teaching assistant for the very first programming course students take, so I have taught C to first year undergrads!
About 30 years ago.
So unless you actually DO teach then don't assume :)
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
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 ;)
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.
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.
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.
> It's small and easy to grasp the entire thing though, unlike most other more modern high level languages.
So ready for the question?
Oh, well it didn't address what I wrote.
> So ready for the question?
Doesn't address what I wrote.
Whatever
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 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.
> 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.
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.
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'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.
@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 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.
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.
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.
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?
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
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.
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.
Of course there are other mechanisms, but for direct bindings I think C bindings are by far the most common.
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.