Skip to content(if available)orjump to list(if available)

I stopped everything and started writing C again

kqr

I started programming with C a long time ago, and even now, every few months, I dream of going back to those roots. It was so simple. You wrote code, you knew roughly which instructions it translated to, and there you went!

Then I try actually going through the motions of writing a production-grade application in C and I realise why I left it behind all those years ago. There's just so much stuff one has to do on one's own, with no support from the computer. So many things that one has to get just right for it to work across edge cases and in the face of adversarial users.

If I had to pick up a low-level language today, it'd likely be Ada. Similar to C, but with much more help from the compiler with all sorts of things.

anta40

Don't forget Pascal is still alive.

bayindirh

Also, COBOL and FORTRAN. FORTRAN is still being developed and one of the languages supported as first class citizen by MPI.

There's a big cloud of hype at the bleeding edge, but if you dare to look beyond that cloud, there are many boring and well matured technologies doing fine.

wruza

From what I remember about Ada, it is basically Pascal for rockets.

sgt

And some call it Boomer Rust, if I recall.

saati

> You wrote code, you knew roughly which instructions it translated to, and there you went!

This must have been a very very long time ago, with optimizing compilers you don't really know even if they will emit any instructions.

kqr

On x86-type machines, you still have a decent chance, because the instructions themselves are so complicated and high-level. It's not that C is close to the metal, it's that the metal has come up to nearly the level of C!

I wouldn't dare guess what a compiler does to a RISC target.

(But yes, this was back in the early-to-mid 2000s I think. Whether that is a long time ago I don't know.)

graycat

When Ada was first announced, I rushed to read about it -- sounded good. But so far, never had access to it.

So, now, after a long time, Ada is starting to catch on???

When Ada was first announced, back then, my favorite language was PL/I, mostly on CP67/CMS, i.e., IBM's first effort at interactive computing with a virtual machine on an IBM 360 instruction set. Wrote a little code to illustrate digital Fourier calculations, digital filtering, and power spectral estimation (statistics from the book by Blackman and Tukey). Showed the work to a Navy guy at the JHU/APL and, thus, got "sole source" on a bid for some such software. Later wrote some more PL/I to have 'compatible' replacements for three of the routines in the IBM SSP (scientific subroutine package) -- converted 2 from O(n^2) to O(n log(n)) and the third got better numerical accuracy from some Ford and Fulkerson work. Then wrote some code for the first fleet scheduling at FedEx -- the BOD had been worried that the scheduling would be too difficult, some equity funding was at stake, and my code satisfied the BOD, opened the funding, and saved FedEx. Later wrote some code that saved a big part of IBM's AI software YES/L1. Gee, liked PL/I!

When I started on the FedEx code, was still at Georgetown (teaching computing in the business school and working in the computer center) and in my appartment. So, called the local IBM office and ordered the PL/I Reference, Program Guide, and Execution Logic manuals. Soon they arrived, for free, via a local IBM sales rep highly curious why someone would want those manuals -- sign of something big?

Now? Microsoft's .NET. On Windows, why not??

lqet

I fully understand that sentiment. For several years now, I have also felt the strong urge to develop something in pure C. My main language is C++, but I have noticed over and over again that I really enjoy using the old C libraries - the interfaces are just so simple and basic, there is no fluff. When I develop methods in pure C, I always enjoy that I can concentrate 100% on algorithmic aspects instead of architectural decisions which I only have to decide on because of the complexity of the language (C++, Rust). To me, C is so attractive because it is so powerful, yet so simple that you can hold all the language features in your head without difficulty.

I also like that C forces me to do stuff myself. It doesn't hide the magic and complexity. Also, my typical experience is that if you have to write your standard data structures on your own, you not only learn much more, but you also quickly see possibly performance improvements for your specific use case, that would have otherwise been hidden below several layers of library abstractions.

This has put me in a strange situation: everyone around me is always trying to use the latest feature of the newest C++ version, while I increasingly try to get rid of C++ features. A typical example I have encountered several times now is people using elaborate setups with std::string_view to avoid string copying, while exactly the same functionality could've been achieved by fewer code, using just a simple raw const char* pointer.

wruza

I also like that C forces me to do stuff myself

I never liked that you have to choose between this and C++ though. C could use some automation, but that's C++ in "C with classes" mode. The sad thing is, you can't convince other people to use this mode, so all you have is either raw C interfaces which you have to wrap yourself, or C++ interfaces which require galaxy brain to fully grasp.

I remember growing really tired of "add member - add initializer - add finalizer - sweep and recheck finalizers" loop. Or calculating lifetime orders in your mind. If you ask which single word my mind associates with C, it will be "routine".

C++ would be amazing if its culture wasn't so obsessed with needless complexity. We had a local joke back then: every C++ programmer writes heaps of C++ code to pretend that the final page of code is not C++.

brucehoult

Try doing C with a garbage collector ... it's very liberating.

Do `#include <gc.h>` then just use `GC_malloc()` instead of `malloc()` and never free. And add `-lgc` to linking. It's already there on most systems these days, lots of things use it.

You can add some efficiency by `GC_free()` in cases where you're really really sure, but it's entirely optional, and adds a lot of danger. Using `GC_malloc_atomic()` also adds efficiency, especially for large objects, if you know for sure there will be no pointers in that object (e.g. a string, buffer, image etc).

There are weak pointers if you need them. And you can add finalizers for those rare cases where you need to close a file or network connection or something when an object is GCd, rather than knowing programmatically when to do it.

But simply using `GC_malloc()` instead of `malloc()` gets you a long long way.

You can also build Boehm GC as a full transparent `malloc()` replacement, and replacing `operator new()` in C++ too.

enriquto

> Try doing C with a garbage collector ... it's very liberating.

> Do `#include <gc.h>` then just use `GC_malloc()` instead of `malloc()` and never free.

Even more liberating (and dangerous!): do not even malloc, just use variable length-arrays:

    void f(float *y, float *x, int n)
    {
            float t[n];  // temporary array, destroyed at the end of scope
            ...
    }
This style forces you to alloc the memory at the outermost scope where it is visible, which is a nice thing in itself (even if you use malloc).

kqr

At first I really liked this idea, but then I realised the size of stack frames is quite limited, isn't it? So this would work for small data but perhaps not big data.

kokada

I think one of the nice things about C is that since the language was not designed to abstract e.g.: heap is that it is really easy to replace manual memory management with GC or any other approach to manage memory, because most APIs expects to be called with `malloc()` when heap allocation is needed.

I think the only other language that has a similar property is Zig.

irq-1

Odin has this too:

> Odin is a manual memory management based language. This means that Odin programmers must manage their own memory, allocations, and tracking. To aid with memory management, Odin has huge support for custom allocators, especially through the implicit context system.

https://odin-lang.org/docs/overview/#implicit-context-system

dlisboa

Which GC is that you’re using in these examples?

umanwizard

I'm not OP but the most popular C GC is Boehm's: https://www.hboehm.info/gc/

bsenftner

About 16 years ago I started working with a tech company that used "C++ as C", meaning they used a C++ compiler but wrote pretty much everything in C, with the exception of using classes, but more like Python data classes, with no polymorphism or inheritance, only composition. Their classes were not to hide, but to encapsulate. Over time, some C++ features were allowed, like lambdas, but in general we wrote data classed C - and it screamed, it was so fast. We did all our own memory management, yes, using C style mallocs, and the knowledge of what all the memory was doing significantly aided our optimizations, as we targeted to be running with on cache data and code as much as possible. The results were market leading, and the company's facial recognition continually lands in the top 5 algorithms at the annual NIST FR Vendor test.

zaphirplane

Sounds like they know what they are doing. How is using c++ with only data classes different from using c with struct

porridgeraisin

Slightly better ergonomics I suppose. Member functions versus function pointers come to mind, as do references vs pointers (so you get to use . instead of ->)

relaxing

Namespaces are useful for wrapping disparate bits of C code, to get around namespace collisions during integration.

rossant

I completely agree with this sentiment. That's why I wrote Datoviz [1] almost entirely in C. I use C++ only when necessary, such as when relying on a C++ dependency or working with slightly more complex data structures. But I love C’s simplicity. Without OOP, architectural decisions become straightforward: what data should go in my structs, and what functions do I need? That’s it.

The most inconvenient aspect for me is manual memory management, but it’s not too bad as long as you’re not dealing with text or complex data structures.

[1] https://datoviz.org/

maccard

> A typical example I have encountered several times now is people using elaborate setups with std::string_view to avoid string copying, while exactly the same functionality could've been achieved by fewer code, using just a simple raw const char* pointer.

C++ can avoid string copies by passing `const string&` instead of by value. Presumably you're also passing around a subset of the string, and you're doing bounds and null checks, e.g.

    const char* Buf = "Hello World" ;
    print_hello(Buf, 6);

string_view is just a char* + len; which is what you should be passing around anyway.

Funnily enough, the problem with string view is actually C api's, and this problem exists in C. Here's a perfect example: (I'm using fopen, but pretty much every C api has this problem).

    FILE* open_file_from_substr(const char* start, int len)
    {
        return fopen(start);
    }

    void open_files()
    {
        const char* buf = "file1.txt file2.txt file3.txt";

        for (int i = 0; i += 10; ++i) // my math might be off here, apologies
        {

            open_file_from_substr(buf + i, buf + i + 10); // nope.
        }
    }

> When I develop methods in pure C, I always enjoy that I can concentrate 100% on algorithmic aspects instead of architectural decisions which I only have to decide on because of the complexity of the language

I agree this is true when you develop _methods_, but I think this falls apart when you design programs. I find that you spend as much time thinking about memory management and pointer safety as you do algorithmic aspects, and not in a good way. Meanwhile, with C++, go and Rust, I think about lifetimes, ownership and data flow.

chronogram

Variety is good. I got so used to working in pure C and older C++ that for a personal project I just started writing in C, until I realised that I don't have to consider other people and compatibility, so I had a lot of fun trying new things.

ManBeardPc

C was my first language and I quickly wrote my first console apps and a small game with Allegro. It feels incredibly simple in some aspects. I wouldn’t want to go back though. The build tools and managing dependencies feels outdated, somehow there is always a problem somewhere. Includes and the macro system feels crude. It’s easy to invoke undefined behavior and only realizing later because a different compiler version or flag now optimizes differently. Zig is my new C, includes a C compiler and I can just import C headers and use it without wrapper. Comptime is awesome. Build tool, dependency management and testing included. Cross compilation is easy. Just looks like a modern version of C. If you can live with a language that is still in development I would strongly suggest to take a look.

Otherwise I use Go if a GC is acceptable and I want a simple language or Rust if I really need performance and safety.

contificate

I sometimes write C recreationally. The real problem I have with it is that it's overly laborious for the boring parts (e.g. spelling out inductive datatypes). If you imagine that a large amount of writing a compiler (or similar) in C amounts to juggling tagged unions (allocating, pattern matching over, etc.), it's very tiring to write the same boilerplate again and again. I've considered writing a generator to alleviate much of the tedium, but haven't bothered to do it yet. I've also considered developing C projects by appealing to an embeddable language for prototyping (like Python, Lua, Scheme, etc.), and then committing the implementation to C after I'm content with it (otherwise, the burden of implementation is simply too high).

It's difficult because I do believe there's an aesthetic appeal in doing certain one-off projects in C: compiled size, speed of compilation, the sense of accomplishment, etc. but a lot of it is just tedious grunt work.

randomNumber7

Despite what some people religiously think about programming languages, imo C was so successful because it is practical.

Yes it is unsafe and you can do absurd things. But it also doesn't get in the way of just doing what you want to do.

ycuser2

I don't think C was successful. It still is! What other language from the 70s is still under the top 5 languages?

https://www.tiobe.com/tiobe-index/

anta40

If you want to do microcontroller/embedded, I think C it still the overall best choice, supported by vendors. Rust and Ada are probably slowly catching up.

zerr

No, it's because of Unix and AT&T monopoly.

relaxing

How was AT&T’s monopoly a driver? It’s not like they forced anyone to use UNIX.

bamboozled

Sounds a bit like perl but at a lower level ?

ThinkBeat

You can certainly do entirely absurd things in Perl. But it is a lot easier / safer work with. You get / can get a wealth of information when you the wrong thing in Perl.

With C segmentation fault is not always easy to pinpoint.

However the tooling for C, with sone if the IDEs out there you can set breakpoints/ walk through the code in a debugger, spot more errors during compile time.

There is a debugger included with Perk but after trying to use it a few times I have given up on it.

Give me C and Visual Studio when I need debugging.

On the positive side, shooting yourself in the foot with C is a common occurrence.

I have never had a segmentation fault in Perl. Nor have I had any problems managing the memory, the garbage collector appears to work well. (at least for my needs)

TinkersW

Eh Segfaults are like the easiest error to debug, they almost always tell you exactly where the problem is.

high_priest

Sounds a bit like JavaScript, but at a tower level?

TingPing

I wouldn’t compare them, C is very simple.

codr7

Yes, but there are similarities, it has the same hacker mind set imo.

TurboHaskal

This reads like a cautionary tale about getting nerdsniped, without a happy ending.

bArray

> Virtual machines still suck a lot of CPU and bandwidth for nothing but emulation. Containers in Linux with cgroups are still full of RCE (remote command execution) and priviledge escalation. New ones are discovered each year. The first report I got on those listed 10 or more RCE + PE (remote root on the machine). Remote root can also escape VMs probably also.

A proper virtual machine is extremely difficult to break out of (but it can still happen [1]). Containers are a lot easier to break out of. I virtual machines were more efficient in either CPU or RAM, I would want to use them more, but it's the worst of both.

[1] https://www.zerodayinitiative.com/advisories/ZDI-23-982/

tromp

Here's what kc3 code looks like (taken from [1]):

    def route = fn (request) {
      if (request.method == GET ||
          request.method == HEAD) do
        locale = "en"
        slash = if Str.ends_with?(request.url, "/") do "" else "/" end
        path_html = "./pages#{request.url}#{slash}index.#{locale}.html"
        if File.exists?(path_html) do
          show_html(path_html, request.url)
        else
          path_md = "./pages#{request.url}#{slash}index.#{locale}.md"
          if File.exists?(path_md) do
            show_md(path_md, request.url)
          else
            path_md = "./pages#{request.url}.#{locale}.md"
            if File.exists?(path_md) do
              show_md(path_md, request.url)
            end
          end
        end
      end
    }
[1] https://git.kmx.io/kc3-lang/kc3/_tree/master/httpd/page/app/...

markus_zhang

C, or more precisely a constrained C++ is my go to language for side projects.

Just pick the right projects and the language shines.

codr7

I've tried, but never succeeded in doing that; the complexity eventually seeps in through the cracks.

C++'s stdlib contains a lot of convenient features, writing them myself and pretending they aren't there is very difficult.

Disabling exceptions is possible, but will come back to bite you the second you want to pull in external code.

You also lose some of the flexibility of C, unions become more complicated, struct offsets/C style polymorphism isn't even possible if I remember correctly.

I love the idea though :)

ryandrake

> C++'s stdlib contains a lot of convenient features, writing them myself and pretending they aren't there is very difficult.

I've never understood the motivation behind writing something in C++, but avoiding the standard library. Sure, it's possible to do, but to me, they are inseparable. The basic data types and algorithms provided by the standard library are major reasons to choose the language. They are relatively lightweight and memory-efficient. They are easy to include and link into your program. They are well understood by other C++ programmers--no training required. Throughout my career, I've had to work in places where they had a "No Standard Library" rule, but that just meant they implemented their own, and in all cases the custom library was worse. (Also, none of the companies could articulate a reason for why they chose to re-implement the standard library poorly--It was always blamed on some graybeard who left the company decades ago.)

Choosing C++ without the standard library seems like going skiing, but deliberately using only one ski.

codr7

The stdlib makes choices that might not be optimal for everyone.

Plenty of code bases also predate it, when I started coding C++ in 1995 most people were still rolling their own.

null

[deleted]

hehbot

As many people have already said, for starting a new project Rust beats C in every way

anymouse123456

Rust is not free of trade offs and you're not helping the cause the way you think you are.

Just a few off the top:

- Rust is a much more complex language than C

- Rust has a much, much slower compiler than pretty much any language out there

- Rust takes most people far longer to "feel" productive

- Rust applications are sometimes (often?) slower than comparable C applications

- Rust applications are sometimes (often?) larger than comparable C applications

You may not value these things, or you may value other things more.

That's completely fine, but please don't pretend as if Rust makes zero trade offs in exchange for the safety that people seem to value so much.

criddell

> helping the cause

Rust evangelism is probably the worst part of Rust. Shallow comments stating Rust’s superiority read to me like somebody who wants to tell me about Jesus.

tcfhgj

it's not unique for Rust, C/C++ devs probably aren't just used to it, since there hasn't been anything major new for decades.

If you already dislike this, I ask you to read C-evangelism with respect to the recent Linux drama about Rust in Linux.

ubercore

Jesus wasn't written in Rust? Sounds like a recipe for UB if you ask me.

checker659

Not to mention, modern CPUs have essentially been designed to make C code run as fast as possible.

codr7

I haven't designed any CPUs myself, someone with more experience could give you more details.

But I don't think this carries much weight anymore, might have been true way back in the days.

C gives you more control, which means it's possible to go faster if you know exactly what you're doing.

imtringued

That's fair, but to me what drags C and C++ really down for me is the difficulty in building them. As I get older I just want to write the code and not mess with makefiles or CMake. I don't want starting a new project to be a "commitment" that requires me to sit down for two hours.

For me Rust isn't really competing against unchecked C. It's competing against Java and boy does the JVM suck outside of server deployments. C gets disqualified from the beginning, so what you're complaining about falls on deaf ears.

I'm personally suffering the consequences of "fast" C code every day. There are days where 30 minutes of my time are being wasted on waiting for antivirus software. Thinks that ought to take 2 seconds take 2 minutes. What's crazy is that in a world filled with C programs, you can't say with a good conscience that antivirus software is unnecessary.

ryandrake

> That's fair, but to me what drags C and C++ really down for me is the difficulty in building them. As I get older I just want to write the code and not mess with makefiles or CMake. I don't want starting a new project to be a "commitment" that requires me to sit down for two hours.

Also, integrating 3rd party code has always been one of the worst parts of writing a C or C++ program. This 3p library uses Autoconf/Automake, that one uses CMake, the other one just ships with a Visual Studio .sln file... I want to integrate them all into my own code base with one build system. That is going to be a few hours or days of sitting there and figuring out which .c and .h files need to be considered, where they are, what build flags and -Ddefines are needed, how the build configuration translates into the right build flags and so on.

On more modern languages, that whole drama is done with pip install or cargo install.

Ygg2

> Rust is a much more complex language than C

Feature wise, yes. C forces you to keep a lot of irreducible complexity in your head.

> Rust has a much, much slower compiler than pretty much any language out there

True. But it doesn't matter much in my opinion. A decent PC should be able to grind any Rust project in few seconds.

> Rust applications are sometimes

Sometimes is a weasel word. C is sometimes slower than Java.

> Rust takes most people far longer to "feel" productive

C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.

Or I can write same stuff in Rust and run tests. Run miri and bigger test suite if I'm using unsafe. Maybe fuzz test.

TingPing

Real projects get into the millions of lines of code, Rust will not scale to compile that quickly.

__d

> C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.

So … make && make check ?

thih9

Good for you. Like the grandparent commenter said, for others these tradeoffs might be important. E.g.:

> I am disappointed with how poorly Rust's build scales, even with the incremental test-utf-8 benchmark which shouldn't be affected that much by adding unrelated files. (...)

> I decided to not port the rest of quick-lint-js to Rust. But... if build times improve significantly, I will change my mind!

https://quick-lint-js.com/blog/cpp-vs-rust-build-times/

fxtentacle

completely not!

(And yes, I was considering if I should shout in capslock ;) )

I have seen so many fresh starts in Rust that went great during week 1 and 2 and then they collided with the lifetime annotations and then things very quickly got very messy. Let's store a texture pointer created from an OpenGL context based on in-memory data into a HashMap...

impl<'tex,'gl,'data,'key> GlyphCache<'a> {

Yay? And then your hashmap .or_insert_with fails due to lifetime checks so you need a match on the hashmap entry and now you're doing the key search twice and performance is significantly worse than in C.

Or you need to add a library. In C that's #include and a -l linker flag. In Rust, you now need to work through this:

https://doc.rust-lang.org/cargo/reference/manifest.html

to get a valid Cargo.toml. And make sure you don't name it cargo.toml, or stuff will randomly break.

steveklabnik

You don’t need to work through that, you can follow https://doc.rust-lang.org/cargo/reference/build-script-examp... and it shows you how.

uasi

Adding `foo = "*"` to Cargo.toml is as easy as adding `-l foo` to Makefile.

mrob

The best feature of C is the inconvenience of managing dependencies. This encourages a healthy mistrust of third-party code. Rust is unfortunately bundled with an excellent package manager, so it's already well on its way to NPM-style dependency hell.

codr7

It's also very mature, not so much of a moving target.

Both aspects are something I think many developers grow to appreciate eventually.

baq

Rust has three major issues:

- compile times

- compile times

- compile times

Not a problem for small utilities, but once you start pulling dependencies... pain is felt.

codr7

Compared to C I'd say the biggest issue is complexity, of which compile time is a consequence.

baq

The C standard makes provisions for compiler implementers which absolve them from responsibility of ignoring the complexity of the C language. Since most people never actually learn all the undefined behavior specified in the standard and compilers allow it, it might seem the language is simpler, but it's actually only compilers which are simpler.

You can argue that Rust generics are a trivial example of increased complexity vs the C language and I'd kinda agree: except the language would be cumbersome to use without them but with all the undefined C behavior defined. Complexity can't disappear, it can be moved around.

anta40

Long compile time isn't a new issue for language with advanced features. Before Rust, it was Haskell. And before Haskell, it was C++.

And implementation wise, probably there's something to do with LLVM.

gpderetta

Compile time is also my top three major issues with C++, in a list that also includes memory safety.

tmtvl

I like the Rust ADTs and the borrow checker, but I can't stand the syntax. I just wish it had Lisp syntax, but making it myself is far beyond my abilities.

dkersten

Except complexity of language

LeonidasXIV

At least apparent complexity. See "Expert C Programming: Deep C Secrets" which creeps up on you shockingly fast because C pretends to be simple by leaving things to be undefined but in the real life things need some kind of behavior.

DeepSeaTortoise

IMO these are the major downsides of Rust in descending order of importance:

- Project leadership being at the whims of the moderators

- Language complexity

- Openly embracing 3rd party libraries and ecosystems for pretty much anything

- Having to rely on esoteric design choices to wrestle the compiler into using specific optimizations

- The community embracing absurd design complexity like implementing features via extension traits in code sections separated from both where the feature is going to be used and where either the structs and traits are implemented

- A community of zealots

I think the upsides easily outcompete the downsides, but I'd really wish it'd resolve some of these issues...

juliangmp

I'll take good complexity over bad simplicity any day.

desdenova

You can ignore most of the complexity that's not inherent to the program you're trying to write.

The difference is C also lets you ignore the inherent complexity, and that's where bugs and vulnerabilities come from.

baq

Rust makes explicit what the C standard says you can't ignore but it's up to you and not the compiler. Rust is a simpler and easier language than C in this sense.

codr7

Not even close to true, may I ask how much experience you have with C (not C++)?

rubymamis

I would use Mojo - you get the type and memory safety of Rust, the simplicity of Python and the performance of C/C++.

pansa2

> simplicity of Python

Python isn’t simple, it’s a very complex language. And Mojo aims to be a superset of Python - if it’s simple, that’s only because it’s incomplete.

wvh

Going from mid-90s assembly to full stack dev/sec/ops, getting back to just a simple Borland editor with C or assembly code sounds like a lovely dream.

Your brain works a certain way, but you're forced to evolve into the nightmare half-done complex stacks we run these days, and it's just not the same job any more.