Remix.run Logo
uecker 5 days ago

Advantages of C are short compilation time, portability, long-term stability, widely available expertise and training materials, less complexity.

IMHO you can today deal with UB just fine in C if you want to by following best practices, and the reasons given when those are not followed would also rule out use of most other safer languages.

simonask 4 days ago | parent | next [-]

This is a pet peeve, so forgive me: C is not portable in practice. Almost every C program and library that does anything interesting has to be manually ported to every platform.

C is portable in the least interesting way, namely that compilers exist for all architectures. But that's where it stops.

snovymgodym 4 days ago | parent | next [-]

> C is not portable in practice. Almost every C program and library that does anything interesting has to be manually ported to every platform.

I'm guessing you mean that every cross-platform C codebase ends up being plastered in cascading preprocessor code to deal with OS and architecture differences. Sure that's true, you still have to do some porting work regardless of the language you chose.

But honestly, is there any language more portable than C? I struggle to come up with one.

If someone told me "I need a performant language that targets all major architectures and operating systems, but also maybe I want to run it on DOS, S390X, an old Amiga I have in my closet, and any mystery-meat microcontroller I can find." then really wouldn't have a better answer for them than C89.

If C isn't portable then nothing is.

simonask 4 days ago | parent [-]

If "portability" to you has to include incredibly esoteric architectures in 2025, then what C has to offer is probably the best you can do, but my point is it doesn't do any better on mainstream platforms either.

If you are targeting any recent platform, both Rust and Zig do what you want.

pjmlp 4 days ago | parent | prev | next [-]

Back in the 2000's I had lots of fun porting code across several UNIX systems, Aix, Solaris, HP-UX, Red-Hat Linux.

A decade earlier I also used Xenix and DG/UX.

That is a nice way to learn how "portable" C happens to be, even between UNIX systems, its birthplace.

uecker 4 days ago | parent | prev [-]

Compilers existing is essential and not trivial (and also usually then what other languages build on). The conformance model of C also allows you to write programs that are portable without change to different platforms. This is possible, my software runs on 20 different architectures without change. That one can then also adopt it to make use of specific features of different platforms is quite natural in my opinion.

simonask 4 days ago | parent [-]

It is essential and nontrivial, but it's also the extremely bare minimum.

You cannot write portable code without platform-specific and even environment-specific adaptations, like handling the presence of certain headers (looking at you, stdint.h and stddef.h), and let's not even start about interacting with the OS in any way.

uecker 4 days ago | parent [-]

There may be platforms that are not conforming to the C standard. But I doubt those then have comprehensive implementations of other languages either.

lifthrasiir 5 days ago | parent | prev [-]

> short compilation time

> IMHO you can today deal with UB just fine in C if you want to by following best practices

In the other words, short compilation time has been traded off with wetware brainwashing... well, adjustment time, which makes the supposed advantage much less desirable. It is still an advantage, I reckon though.

uecker 4 days ago | parent [-]

I do not understand what you are tying to say, but it seems to be some hostile rambling.

lifthrasiir 4 days ago | parent [-]

Never meant to be hostile (if I indeed were, I would have question every single word), but sorry for that.

I mean to say that best practices do help much but learning those best practices take much time as well. So short compilation time is easily offseted by learning time, and C was not even designed to optimize compilation time anyway (C headers can take a lot to parse and discard even when unused!). Your other points do make much more sense and it's unfortunate that first points are destructively interfering each other, hence my comment.

uecker 4 days ago | parent [-]

Sorry, maybe I misread your comment. There are certainly languages easier to learn than C, but I would not say C++ or Rust fall into this category. At the same time, I find C compilation extremely fast exactly because of headers. In C you can split interface and implementation cleanly between header and c-file and this enables efficient incremental builds. In C++ most of the implementation is in headers, and all the template processing is order of magnitude more expensive than parsing C headers. Rust also does not seem to have proper separate compilation.

steveklabnik 4 days ago | parent [-]

> I find C compilation extremely fast exactly because of headers.

The header model is one of the parts that makes compiling C slower than it could be. This doesn't mean that it is slow, but it's fast in spite of headers, not because of them.

> In C you can split interface and implementation cleanly between header and c-file and this enables efficient incremental builds.

That's not what does, it is the ability to produce individual translation units as intermediary files.

> Rust also does not seem to have proper separate compilation.

Rust does separate compilation, and also has efficient incremental builds. Header files are not a hard requirement for this.

uecker 4 days ago | parent [-]

If you say the header model makes it slower than it could be, you need to compare it to something. I do not see how it causes significant slow downs in C projects (in contrast to C++). And yes, I wrote compilers and (incomplete) preprocessors. I do not understand what you mean by your second point. What separation of interface and implementation allows you to do is updating the implementation without having to recompile other TUs. You can achieve this is also in different ways, but in C this works by in this way.

I am not sure how it works in Rust as you need to monomorphize a lot of things, which come from other crates. It seems this would inevitably entangle the compilations.

steveklabnik 4 days ago | parent [-]

> I do not see how it causes significant slow downs in C projects

It's that textual inclusion is just a terrible model. You end up reprocessing the same thing over and over again, everywhere it is used. If you #include<foo.h> 100 times, the compiler has to reparse those contents 100 times. Nested headers end up amplifying this effect. It's also at a file-level granularity, if you change a header, every single .c that imports it must be recompiled, even if it didn't use the thing that was changed. etc etc. These issues are widely known.

> I do not understand what you mean by your second point. What separation of interface and implementation allows you to do is updating the implementation without having to recompile other TUs.

Sure, but you don't need to have header files to do this. Due to issues like the above, they cause more things to be recompiled than necessary, not less.

> You can achieve this is also in different ways, but in C this works by in this way.

Right, my point is, those other ways are better.

> I am not sure how it works in Rust as you need to monomorphize a lot of things, which come from other crates. It seems this would inevitably entangle the compilations.

The fact that there are "other crates" is because Rust supports separate compilation: each crate is compiled independently, on its own.

The rlib contains the information that, when you link two crates together, the compiler can use for monomorphization. And it's true that monomorphization can cause a lot of rebuilding.

But to be clear, I am not arguing that Rust compilation is fast. I'm arguing that C could be even faster if it didn't have the preprocessor.

uecker 4 days ago | parent [-]

> It's that textual inclusion is just a terrible model. You end up reprocessing > the same thing over and over again, everywhere it is used.

One could certainly store the interfaces in some binary format, but is it really worth it? This would also work with headers by using a cache, but nobody does it for C because there is not much to gain. Parsing is fast anyhow, and compilers are smart enough not to look at headers multiple times when protected by include guards. According to some quick measurements, you could save a couple of percent at most.

The advantages of headers is that they are simple, transparent, discoverable, and work with outside tools in a modular way. This goes against the trend of building frameworks that tie everything together in a tightly integrated way. But I prefer the former. I do not think it is a terrible model, quite the opposite. I think it is a much better and nicer model.