Obvious Things C Should Do
50 comments
·January 11, 2025chacham15
While the author has WAY more knowledge/experience than me on this and so I wonder how he would solve the following issues:
Evaluating Constant Expressions
- This seems really complicated...if you're working within a translation unit, thats much simplified, but then you're much more limited in what you can do without repeating a lot of code. I wonder how the author solves this.
Compile Time Unit Tests
- This is already somewhat possible if you can express your test as a macro, which if you add in the first point, then this becomes trivial.
Forward Referencing of Declarations
- I think there may be a lot of backlash to this one. The main argument against this is that it then changes the compiler from a one-pass to two pass compiler which has its own performance implications. Given the number of people who are trying to compile massive codebases and go as far as parallelizing compilation of translation units, this may be a tough pill for them to swallow. (evaluating constant expressions probably comes with a similar/worse performance hit caveat depending on how its done)
Importing Declarations
- This is a breaking change...one of the ways I have kind of implemented templating in C is by defining a variable and importing a c file, changing the variable, and then reimporting the same c file. Another thing I've done is define a bunch of things and then import the SQLite C Amalgamation and then add another function (I do this to expose a SQLite internal which isnt exposed via its headers). All of these use cases would break with this change.
Are there any thoughts about these issues? Any ways to solve them perhaps?
WalterBright
> if you're working within a translation unit, thats much simplified, but then you're much more limited in what you can do without repeating a lot of code. I wonder how the author solves this.
You are correct in that the source code to the function being evaluated must be available to the compiler. This can be done with #include. I do it in D with importing the modules with the needed code.
> This is already somewhat possible if you can express your test as a macro, which if you add in the first point, then this becomes trivial.
Expressing the test as a macro doesn't work when you want to test the function. The example I gave was trivial to make it easy to understand. Actual use can be far more complex.
> Performance
D is faster at compiling than C compilers, mainly because:
1. the C preprocessor is a hopeless pig with its required multiple passes. I know, I implemented it from scratch multiple times. The C preprocessor was an excellent design choice when it was invented. Today it is a fossil. I'm still in awe of why C++ has never gotten around to deprecating it.
2. D uses import rather than #include. This is just way, way faster, as the .h files don't need to be compiled over and over and over and over and over ...
D's strategy is to separate the parse from the semantic analysis. I suppose it is a hair slower, but it also doesn't have to recompile the duplicate declarations and fold them into one.
Compile time function execution can be a bottleneck, sure, but that (of course) depends on how heavily it is used. I tend to use it with a light touch and the performance is fine. If you implement a compiler using it (as people have done!) it can be slow.
> one of the ways I have kind of implemented templating in C is by defining a variable and importing a c file, changing the variable, and then reimporting the same c file. Another thing I've done is define a bunch of things and then import the SQLite C Amalgamation and then add another function (I do this to expose a SQLite internal which isnt exposed via its headers). All of these use cases would break with this change.
I am not suggesting removing #include for C. The import thing would be additive.
> Are there any thoughts about these issues?
If you're using hacks to do templating in C, you've outgrown the language and need a more powerful one. D has top shelf metaprogramming - and as usual, other template languages are following in D's path.
chacham15
Thanks for taking the time to respond! I have a few followup questions if thats ok:
> You are correct in that the source code to the function being evaluated must be available to the compiler. This can be done with #include. I do it in D with importing the modules with the needed code.
> D's strategy is to separate the parse from the semantic analysis. I suppose it is a hair slower, but it also doesn't have to recompile the duplicate declarations and fold them into one.
I dont quite follow all the implications that these statements have. Does the compiler have a different way of handling a translation unit?
- Is a translation unit the same as in C, but since you're #including the file you would expect multiple compilations of a re-included C file? woudnt this bloat the resulting executable (/ bundle in case of a library)
- Are multiple translation units compiled at a time? Wouldnt this mean that the entire translation dependency graph would need to be simultaneously recompiled? Wouldnt this inhibit parallelization? How would it handle recompilation? What happens if a dependency is already compiled? Would it recompile it?
> Performance
I think a lot of this is tied to my question about compilation/translation units above, but from my past experience we have "header hygene" which forces us to use headers in a specific way, which if we do, we actually get really good preprocessor performance (a simple example being: dont use #include in a header), how would you compare performance in these kinds of situations vs a compiler without (i.e. either recompiled a full source file or looking up definitions from a compiled source)?
> If you're using hacks to do templating in C, you've outgrown the language and need a more powerful one. D has top shelf metaprogramming - and as usual, other template languages are following in D's path.
yes, as also demonstrated in the performance question, we do a lot to work within the confines of what we have when other tools would handle a lot more of the lifting for us and this is a fair criticism, but on the flip side, I dont have the power to make large decisions on an existing codebase like "lets switch languages" (even if for a source file or two...I've tried) as much as I wish I could, so I have to work with what I have.
daymanstep
Can't you use precompiled headers?
WalterBright
Interesting you brought that up. I implemented them for Symantec C and C++ back in the 90s.
I never want to do that again!
They are brittle and a maintenance nightmare. They did speed up compilations, though, but did not provide any semantic advantage.
With D I focused on fast compilation so much that precompiled headers didn't offer enough speedup to make them worth the agony.
billfruit
Every other language does seems to not require header file/forward declarations. I don't understand the backlash against that.
Are modern C compilers actually still single pass?
xigoi
I personally don’t like forward referencing because it makes code harder to read. You can no longer rely on the dependency graph being in topological order.
WalterBright
As the article writes, that forces the private leaf functions to be at the top, with the public interface at the end of the file. The normal way is the public interface at the top, and the implementation "below the fold", so to speak.
> topological order
You are correct. But its the reverse topological order, which is not the most readable ordering. One doesn't read a newspaper article starting at the bottom.
xigoi
Maybe it’s because I’m primarily a mathematician, but I like building complex stuff up from primitives and having the most important results at the end.
acheong08
It really reads like the author just wants Zig.
robterrell
The author is the creator of D, so he's probably fine with D. And D is something like 25 years old. Whereas Zig is just a toddler.
WalterBright
Zig copies features from D!
koolba
Imitation is the sincerest form of flattery.
WalterBright
Yup. D is the source for a number of recent features in other languages.
The reason I embarked on D is because C and C++ were too reluctant to move forward.
throwawaymaths
more importantly, though, zig deliberately doesnt implement a whole TON of things that D does.
sometimes parsimony is called for. zig is basically c--+ where the + is the constexpr stuff.
WalterBright
It keeps adding D features anyway, like constexpr.
convolvatron
or maybe even D
TinkersW
C++ has all of these except forward referencing declarations(though Importing Declarations requires modules which nobody uses yet).
I'm not sure why forward reference declarations is needed nowadays(or if it really is from a language standpoint).
C could probably copy C++'s constexpr & static_assert stuff to get the first 2.
dccsillag
If I'm not mistaken, the treatment of forward declarations proposed in the article actually breaks the C standard, which would be a rather pressing concern. As far as I am aware, that is the reason why things are the way they are right now in C land.
(In the past, there were more legitimate concerns on the ease of implementation. Nowadays, as the article points out, they are pretty moot, other than having to keep backwards-compatibility.)
I'm also rather bothered that on the bit on const execution in the article, there was no discussion on how to deal with functions that may not terminate or take rather long to execute. Especially considering the unit tests motivation, this seems like a rather blaring omission.
WalterBright
How does it break the C Standard?
> how to deal with functions that may not terminate or take rather long to execute
Control-C, the same as when running any executable that shouldn't be taking that long. It doesn't solve the halting problem :-/
WalterBright
Nobody uses C++ modules because they are clumsy to use. D's are easy. Note: anyone is free to copy D's module design. It's the best one out there.
> I'm not sure why forward reference declarations is needed nowadays
The article gives reasons. Although they aren't necessary, they are deleterious to code layout which becomes a slave to the declaration order rather than aesthetic order.
> C++'s constexpr
is still lagging behind D's, after 17 years of development. In D, the garbage collector makes memory allocation in it trivial. Furthermore, only the path taken through a function needs to be CTFE-compatible, the path not taken does not.
WalterBright
I didn't know X put articles behind a paywall? I haven't tried putting articles there before.
Anyhow, here's the same article:
https://www.digitalmars.com/articles/Cobvious.html
Fun fact: X's article formatter recognizes D code!
wging
I'm not sure what you are seeing, but perhaps it's just a login wall. I was able to read it; I'm logged in but have never paid for Twitter or X. X does tend to hide certain things (such as replies and replied-to tweets) if you're not logged in.
WalterBright
I'm already logged in, which I infer is why I didn't see a login wall. Anyhow, I also posted an alternate link.
kurisufag
it's not visible to nitter, which is annoying.
honestSysAdmin
Perhaps I could load the Javascript and also "login to X", but I'll instead forego reading this and pay attention to what others are writing about C.
EuAndreh
Good suggestions, but also meh, e.g.: forward declaration requirement enables a single-pass compiler to emit code on-the-fly.
I have a much better list for things to add to C: Nothing. C isn't perfect, or nearly as good as it could be, but simply adding things onto C gets you C++.
Adjusting what sircmpwn says: in C you don't solve problems by adding features, but by writing more code in C.
I liked an answer on stack overflow on a question on "how to write a generic function wrapper in Go", or something similar. Many suggestions included reflection, but the author wanted something simpler with varargs without reflection. A comment simply said: "wrong language".
I'd rather adopt this position for some languages, instead of add more and more to C3X. I do away with things in C23, and don't want even more things added in to C.
Making a strech of OP's arguments: "look at all this cool things that C could do, and that D does!". Well, go on and use D, nothing wrong with that.
(BTW, I do write test targets for every file in my C projects, but I'm not so much into jogging).
Those things aren't that obvious, and I'd rather not have them added to C.
Wrong language.
WalterBright
> forward declaration requirement enables a single-pass compiler to emit code on-the-fly.
True, I know all about that. My Zortech C and C++ compiler was one pass (after the multiple preprocessing passes). The ground up ImportC C compiler completed a couple years ago has a separate parse pass.
So I well know the tradeoffs. The parser being stand-alone means it is much simpler to understand and unittest. I found no advantage to a single pass compiler. It isn't any faster.
> simply adding things onto C gets you C++
C++ doesn't allow forward declarations either.
Successfully doing a parse-only on C code doesn't quite work. It turns out the grammar relies on a symbol table. Fortunately, only a symbol table of the typedefs. Once adding that in, ImportC worked. (I really tried to make it work without the typedef symbol table!)
C++ added a bunch more syntax that relies on the symbol table. I would not even try fixing it to work as parse-only.
> in C you don't solve problems by adding features, but by writing more code in C
The trouble with such sayings is like following a google map that says cross this bridge, but wasn't updated with news that the bridge is out.
> Those things aren't that obvious,
They are once you use another language that doesn't have those restrictions.
> and I'd rather not have them added to C.
C adds new things all the time to the Standard, like normalized Unicode identifiers, which are a complete waste of time. Every C compiler also adds a boatload of extensions, some good, some wacky, many ineptly documented, all incompatible with every other C compiler extensions.
EuAndreh
I have my own list of things that could "easily" be added to C, but I'd rather them not to be.
WalterBright
You get them anyway in the form of extensions.
meibo
Sorry to be off-topic, but I'm disappointed in this being on X, it even comes with an ad built-in. We should be doing better. Spinning up a single-site blog is easier than ever for technically-minded folks - the internet needs to become independent again.
ranger_danger
login-walled
dwattttt
Curious. I don't have an account, and I can read it.
acheong08
Looks like a new feature. Articles don't render render on xcancel
null
smitty1e
Have a go at this => https://t.co/KXRE5XvuoP
I used the https://publish.twitter.com thing against the Xeet, then lifted the first HREF out of the embed goo.
ryukoposting
Yeah... no.
Constexpr function evaluation sounds like a great idea until you start trying to use it, and get surprised when seemingly-constexpr-safe functions aren't constexpr. Or, you tweak one function and suddenly all your fancy compile-time unit tests explode.
Ok, so you get around that with good code hygiene and by limiting the complexity of your constexpr functions... in other words, do the exact things we already do with preprocessor macros.
Alternatively, you add a constexpr keyword to the lang, but now we have red functions and blue functions. Great.
In another language, there'd still be an argument for the type-safety that would precipitate from constexpr function eval, but this is C we're talking about.
How about container_of? Could we please standardize that already? Why is this crucial and immensely useful macro a thing we all copy-paste from that one page on kernel.org?
foul
Not again, not another D thread noooooooooooooooooo (i'm joking)
WalterBright
All your base belong to D!
> Evaluating Constant Expressions
The examples are quite simple in the article but I believe more complex cases would significantly degrade the compiler speed (and probably the memory footprint as well) and would require a VM to leverage this.
Which is probably assumed "too complex" to go into the standard. I'm not saying it's impossible, but I kind of understand why this would not go into any kind of standard.
> Importing Declarations
I wish C++ (or even C) would have gone into this direction instead the weird mess of what is defined for C++20.
Additionally you might import module into some symbol, like:
and every non-static symbols from the file can be accessed from like: > __import dex;This is totally tangential but I don't like when file paths are not explicit. In this specific case I don't know if I'm importing dex.d or dex.c.