
I love coding in C - lordleft
https://lord-left.github.io/posts/love-coding-c.html
======
caseymarquis
Coding in C is like camping. It's fun for a while, but eventually you really
miss things like flushing toilets and grocery stores. I like using C, but I
get frustrated every time I hit a wall trying to build features from other
languages. C is a very WET language.

C is basically it for embedded development though. I've gotten so tired of
recompiling and waiting to flash a chip with C, that I've started learning
ANTLR and making my own language. The idea is to have a language which runs in
a VM written in C, and allows you to easily access code which has to be in C.
Sort of like uPython, except it can easily be used on any board with a C
compiler with a small amount of setup, and C FFI is a first class citizen.
Also coroutines masquerading as first class actors with message passing, since
I always end up building that in C anyway.

~~~
retrac
There's an old quote, "Greenspun's tenth rule"
[https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule](https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule)
:

"Any sufficiently complicated C or Fortran program contains an ad-hoc,
informally-specified, bug-ridden, slow implementation of half of Common Lisp."

~~~
Const-me
An old quote indeed.

In modern world, any sufficiently complicated C or C++ program embeds a
runtime of some higher level language. More often than not, that language is
formally specified, well-supported, and fast.

Sometimes it’s indeed LISP like in AutoCAD, but more popular choices are
JavaScript (all web browsers), LUA (many video games and much more, see
Wireshark), Python, VBScript or VBA (older Windows software), .NET runtime
(current Windows software and some cross-platform as well, like Unity3D game
engine). These days, it’s rarely a custom language runtime, but it still
happens, like Matlab.

~~~
d33
Which of those is actually formally specified? Also, calling Python or
JavaScript any effieicnt sounds a bit unwarranted. Consider any JavaScript
desktop application, its power and memory usage... it's a mess.

~~~
anchpop
> Consider any JavaScript desktop application, its power and memory usage...
> it's a mess.

I'm running VSCode right now with many tabs open and extensions running, and
it's using less than 100mb of memory. Doesn't seem so bad to me

~~~
diego_moita
> using less than 100mb of memory. Doesn't seem so bad to me

You know... any embedded programmer reading that comment is probably laughing
hysterically. In most cheap or energy efficient microcontrolers, more than 4
Mb is considered a luxury.

~~~
UncleMeat
Ok.

There is no virtue in using less memory for the sake of it. If a desktop
application is taking 100mb, you can have a _lot_ of those running before
modern boxes start to struggle.

------
yogthos
A lot of people have a misconception that C is close to the way hardware
works. The reality is that hardware engineers jump through lots of hoops so
that C programmers can keep pretending that they're coding against a really
fast PDP-11. Not only that, but these contortions directly lead to
vulnerabilities like Meltdown and Spectre because memory representation in C
doesn't map well to modern hardware. C family of languages is basically
holding us back from utilizing current day hardware safely and efficiently.
This [1] is an excellent essay on the topic.

[1]
[https://queue.acm.org/detail.cfm?id=3212479](https://queue.acm.org/detail.cfm?id=3212479)

~~~
nickelpro
What a load of crap

Without cache coherency and ILP, programming in any language would be insane.
It's not like programming in x86_64 assembly suddenly opens a world of
possibilities because its "truly low-level". You gain very little extra
control over a given platform by switching to assembly over C, that's what we
mean by "low-level".

C maps cleanly onto the instruction sets provided by chip manufacturers. It
provides the option to the programmer to optimize structures for use in
vectorization if they so choose, or to optimize for some other objective like
size for a binary wire protocol or limited memory space.

The very nature of having a choice about memory layout of structures and the
ability to cleanly link with the platform ABI is what makes C low-level.
Obviously the inner-workings of a modern CPU don't map cleanly to the C
virtual machine. However, there's no convincing evidence that greater control
over cache invalidation is what's holding back performance of those CPUs.

~~~
sigjuice
If finer-grained cache control is needed, then it is no big deal to invoke the
appropriate compiler intrinsics (if available) or write your own with inline
assembly. My preference is to put these sorts of things in a separate .S file.

------
linuxlizard
I've been writing a large app in C again after a few years away. Originally
wrote the app in C++ then again in Python and now in straight C. I enjoy the
faster compile times of C (over C++) and the better type checking of C (over
Python).

However, when I'm carefully using C++, I don't have to fiddle around with
memory management and still get fast performance and better type checking.

Yesterday I wrote a Python script to generate C code test cases. String
manipulation in Python is very easy.

The right tool for the right job, I suppose.

~~~
umvi
> Yesterday I wrote a Python script to generate C code test cases.

I do this quite a bit. Basically, anytime there is something very boiler-platy
(like HTTP APIs, configuration management, test cases, etc.) I write metadata
in JSON that _describes_ what I'm adding, and then at build time a python
script will parse the metadata and generate all of the C or C++ which is then
compiled. I even have the python generating comments in the generated C/C++!

I used to use Jinja2 for generating the C/C++ files, but now I just use
f-strings in the latest version of vanilla Python3.

~~~
thibran
This is what Lisp/Scheme based languages use macros for -> to reduce
boilerplate code. A lot of programmers hate DSL's, but using Python to
generate test code is the same as using a DSL. The difference is, Python is a
much bigger and more complicated DSL, than a test-macro will probably ever be.

There is nothing wrong with generating code in a pragmatic way. It is just
strange to see all the time people using workarounds for things that a tiny
DSL could probably solve better.

~~~
umvi
There's another advantage to using a high level language like python though -
since the "source" is now JSON metadata which is decoupled from the code, I
can use that metadata to generate other interesting things. For example, I can
use python-docx to generate a Word document, for example (which my company
requires for documentation tracking). Or, if the metadata is describing HTTP
APIs, I can generate an OpenAPI document which can be fed into Swagger UI[1].

[1] [https://swagger.io/tools/swagger-ui/](https://swagger.io/tools/swagger-
ui/)

------
jacquesm
In August this year I started a new project. In C. Why? Because I'm quite
familiar with it and because it allowed me to focus on the problem rather than
on the language that I was writing in (the problem is - for me at least - a
very hard one). The codebase has steadily grown, at some point it was 7K
lines, then I cleaned it up and refactored it back down to about 5500 right
now. At some future date it will be - hopefully - finished and I may use it to
power applications and/or services. It's super efficient both memory and speed
wise compared to what the competition is doing but that's just a side effect.
That side effect does allow for some applications that would otherwise be
impossible.

The code takes one file and outputs another, it's a classical 'unix filter'
and it relies on a couple of very high performance libraries that others
provided, battle tested and ready to be plugged in. As the project matures I
come across the occasional wart in the language, something that I know I could
have done more elegantly in a language with richer constructs (lists, list
comprehensions, that sort of thing).

What surprised me most after working on this project for a couple of months
though is how well the language fits the problem, that's something that I did
not really think would be the case when I started out with it. But as time
passes and things 'find their place' it is indeed just like the author writes,
I - still - love coding in C. Even if I know there are better alternatives out
there and I should probably get invested in one of them one of these days at
the 75% mark or so of my career I'm still _very_ happy that I picked C at the
start and never once did I imagine that it would still serve me this well 37
years later.

Most of the other 'hot' languages of the days that went by in the meantime
have all been lost, and yet, C is still here, and likely will still be here
for years to come. To me it's like an old knife. Not pretty, known to hurt you
if you abuse it but still plenty sharp and well capable of doing the job if
you treat it well.

~~~
roca
Experience shows that almost every time someone writes a project like this in
C, applying modern fuzzing tools (e.g. AFL) will uncover bugs that let a
maliciously crafted input file hijack execution and act with the privileges of
whatever's running the code. It is a lot of work to remove all such bugs (and
not reintroduce any) and you never know when you're done. That is a very
severe problem with C that is having devastating effects on society.

It is therefore irresponsible to use C for a new project like this, unless you
know your code will never be exposed to malicious input. (Note that if you
share the code with anyone else, it's hard to have such confidence.)

~~~
jacquesm
> It is therefore irresponsible to use C for a new project like this

So, given all this doom that hangs above my head, what should I have used?

~~~
ncmncm
C++ is easy to transition to.

The powerful libraries growing up around it make dropping down to the
dangerous level mentioned largely unnecessary, without giving up performance.

~~~
roca
Every time you use references in C++ you are "dropping down to the dangerous
level".

~~~
ncmncm
Passing things by reference is safe. Any other use is mostly just pointless;
it what reference types are for. If you are doing something else with them, it
easy to stop.

~~~
roca
Passing parameters by reference is certainly not always safe. You can, for
example, pass a reference to a deref'ed unique_ptr into a function call, while
the function call does something that clears the unique_ptr.

If you want to avoid objects containing references (or raw pointers), then you
have to avoid using lots of standard library stuff --- `string_view`, C++20
`span`, or even STL iterators.

~~~
ncmncm
Now you are talking about pointers.

~~~
roca
There isn't much difference between references and pointers. That is the
problem.

~~~
ncmncm
A reference only ever points at one thing, from birth to death, and it always
points at something.

~~~
roca
No, it is entirely possible to delete the underlying object so a reference is
left dangling. See
[https://github.com/isocpp/CppCoreGuidelines/issues/1038](https://github.com/isocpp/CppCoreGuidelines/issues/1038)
for example.

------
reza_n
I write C full time and I love it (Varnish Cache). In our team of about 10
full time C engineers, we spend less than an hour a month dealing with things
like “memory safety”. When you are writing C at a professional level, your
time is spent on things like performance, algorithms, accuracy, hitting
requirements, and delivering software. We have numerous safety and fuzzing
systems humming in the background checking our work 24/7\. The tooling
ecosystem (Linux) is top notch.

(If you want to write C full time professionally too, contact me!)

~~~
helixes
What does it take to become a professional C engineer? How did you practice
all that. I currently started looking into it, but I'm kind of just getting
started (Books/PDF, Exercises).

~~~
reza_n
Time and experience. Learning the syntax and wrapping your head around
pointers and memory is the first step. After that, just try and write as much
C as possible. Help out with projects, start projects, write tools and APIs.
Most importantly, study existing code bases to learn real world techniques.
Best case, get a C job where you have to write a diverse amount of C code day
after day.

------
codr7
I've spent roughly 20 years going back and Forth between C and C++, trying to
decide whether to add the missing pieces my way to C or strip away what I
don't need from C++. I'm currently in a C phase after having spent quite some
time writing interpreters in C++.

The most common issue I see in C codebases is excessive memory allocation. In
most other languages, allocating a chunk per instance is the only way, though
the implementation likely does some kind of pooling/slab allocation behind the
scenes. In C you have the freedom to arrange your allocations any way you feel
like, and treat any segment of N bytes as a value of size N. As an example,
most collections (vectors, sets, maps etc); tend to only work with pointers;
which is a huge missed opportunity.

My two favorite kinds of collections in C are embedded linked lists [0] and
deques. One reason is they both provide stable pointers (which is the main
disadvantage of vectors), the other that they minimize the number of memory
allocations.

I recently implemented a pool allocated deque library [1] in C that uses
embedded linked lists to keep track of memory blocks and provides value
semantics, which I hope will make my interpreters easier to implement as well
as increase performance. Time will tell.

[0]
[https://github.com/codr7/libceque/blob/master/source/libcequ...](https://github.com/codr7/libceque/blob/master/source/libceque/list.h)
[1] [https://github.com/codr7/libceque](https://github.com/codr7/libceque)

------
decasia
I thought the OP was a little bit self-undermining, because as the author
points out, C feels "close to the metal" but at this point it's actually
virtual metal.

But I think "getting close to the lower levels" is still great. The only time
I write C-like code is for an Arduino. Turns out that even if your electronics
is rusty, you can do some very useful things in the real world by automating a
simple motor and a few LEDs. And whenever you have to interface with
individually numbered output pins, it feels pretty low level.

tldr: if you want to get close to the metal, it's really useful to start by
switching to hardware that facilitates this.

~~~
saagarjha
Even assembly goes through a number of layers before it’s run. With microcode,
virtual memory, and privilege rings there’s a number of things between the
code and what it’s running on.

~~~
decasia
Yeah, that's why I used expressions like "closer to the lower levels" — you
can descend in orders of abstraction/layers of reality, but it's more
asymptotic or directional than anything else.

It's not like playing with an Arduino is getting me closer to understanding
the underlying chemistry and physics. Which are also interesting to learn
about...

But personally I think it's for the best that reality is inexhaustibly complex
(from the perspective of human individuals).

------
roca
C has not really been close to the metal for a long time. The C abstract
machine is actually very complicated, especially the rules around undefined
behavior. John Regehr's blog explains this well, e.g.
[https://blog.regehr.org/archives/213](https://blog.regehr.org/archives/213).
It's easy to write code that looks like it will compile to something simple
and obvious but that current compilers will turn into code that does something
else entirely, or worse, current compilers compile as you expect but future
compilers will mangle.

If you want to be reliably close to the metal, I think the best options are
either to actually write assembly, or use a language with a simpler abstract
machine (preferably, no undefined behavior), that statically compiles to code
that maps closely to what you wrote, and lets you generate "idiomatic machine
code" (e.g. unboxed integers). Safe Rust fits the bill, but there are probably
other more obscure options.

------
nlh
This is timely! I never really learned C deeply (other than from a class in
high school where pointers were considered 'too advanced'...so we didn't
really get too far).

I decided to work on this year's Advent of Code in C and it's been a blast so
far. I finally "get" pointers, malloc/free, etc. (and I have a newfound
appreciation for Ruby things like CSV.each).

Since most of my coding work is at a much much higher level (web backend &
frontend stuff), I probably won't spend too much time in C in the regular
course of business, but it's massively helped me better have an intuitive
understanding of previously vague things like "allocate on the stack vs. heap"
and "pass by reference vs. value" \-- I knew what those words all meant but
didn't really have a feel for what they actually did. C helps with that.

------
anderspitman
Coming back to C always feel very refreshing, until I have to manipulate
strings or open a socket. Currently golang is striking the right balance for
me. Rust has a place as well, but it still slows me down too much. When
writing Rust there's sort of this constant background anxiety that I won't be
able to get the compiler to accept the thing I'm trying to do. I'm excited
about async/await though, and when you must have safety+performance it's
awesome.

~~~
MiroF
I think it really depends on what you are writing. I built up a column-store
database in C recently (not for production, just as a project) and while I can
imagine rebuilding it in C++/Rust, I don't think it would be possible to do it
in a performant way in Go.

Go is nice, but tends to be very verbose and also is garbage collected. It's
for a different usecase.

~~~
anderspitman
Yeah I could have been more clear. Vs C, I use Go for productivity tradeoffs
(strings, networking, etc). For performance I would use Rust.

------
TazeTSchnitzel
C is low-level enough to hang yourself and high-level enough to make it hard
to tell when you've done so. It was a great language in the 1970's.

~~~
bachmeier
And is now obsolete, for the most part. Unless you want to do something on
hardware that _only_ has C support, there are generally better options. The
language has not evolved. There are now several good alternatives in this
space.

Edit: Rather than responding to all the comments individually: There's
obviously a lot of code already written in C. That doesn't mean it makes sense
to write new code in C. Sometimes C is your only option, which I already said
in my original comment.

None of that means there is any reason to start a new project in C if it's
going to run on Windows or Linux, and it's certainly not the case that you
have to write your program in C if you want to call it from another language.
Sorry if this disappoints anyone, but it's true. For the traditional uses of
C, such as writing a text editor or a library for numerical computing that
you're going to call from another language, there's no longer a sensible
argument for using C.

~~~
codr7
Except the entire world basically runs on top of C, and any library that wants
to be usable from different languages is still written in it. Which is a
pretty weird definition of obsolete. Don't use it if you don't want to for
whatever reason, but spreading FUD to protect your bubble doesn't help anyone.
C is the most successful programming language ever designed, and a fine tool
in the right hands. It's not perfect, nothing is, anyone telling you otherwise
is lying.

~~~
ChrisSD
> any library that wants to be usable from different languages is still
> written in it.

This part isn't quite true. All that's needed is to be able to define an
exported function with a specific name and calling convention. Libraries can
be written in any language that supports these features.

~~~
codr7
I used to do some of that in Borland's Object Pascal, which sort of works but
isn't much fun.

Even C++ is a pain in the behind, because everything that doesn't translate
needs to be encapsulated.

As a result, it's very rare to find code written in different languages with
exported C APIs from my experience.

------
fortyrod
I'm finishing up an embedded side project over the holidays. It's C, FreeRTOS
and the STM USB host stack. I spend most of my time with the likes of React
JS, Swift and Kotlin so it does give me a good, honest, fresh feeling. Also
the feeling that my code could rise up and smite me at any moment! Great read,
thanks for posting!

------
haunter
One of my early Youtube memory is the "Write In C" song altho I think I saw it
written on some Usenet channels in the mid-90s

[https://youtu.be/XHosLhPEN3k](https://youtu.be/XHosLhPEN3k)

~~~
spion
[https://imgur.com/gallery/2O5zM](https://imgur.com/gallery/2O5zM)

------
mechhacker
I _really_ like programming in Rust, when I do.

Got as far as playing around with a web site that used Rustler NIFs on a
Phoenix website. Why? It was just fun to figure out how to get something from
the web frontend through Elixir and into some Rust numerical code.

I don't think it had much practical use and knowing me, would be ridiculous to
go back and touch it again (likely). I've been thinking about doing Rust to
WASM but I'm still not sure if it's just throwing a bunch of complexity at
something _just because_. It holds my interest a lot more than just doing
everything in JS, though.

C was also more enjoyable to me than other languages. Something about compiled
languages just feels really nice. But, it's also great to have a REPL
available right there. Like with Elixir.

~~~
dnautics
If you like low level nifs in elixir, check out my project:
[https://hexdocs.pm/zigler/Zigler.html](https://hexdocs.pm/zigler/Zigler.html)

------
edwinyzh
I LOVE coding in Delphi, really. And I start my new project (document-focused
website generator @ [https://docxmanager.com](https://docxmanager.com)) in
Delphi, while some people think it's dead :)

And I like C (although I don't code in it), because Delphi/Lazarus can
interface with C very easily, you know, there is a lot of C libraries around
the world :)

------
diego_moita
Pick the right tool for each job.

C is fantastic for small and very optimized components. If you want it
portable, from microcontrolers to GPUs, it is the best choice too.

But if you need rapid prototyping, large scale projects within an organization
or some libraries and tools to make the perfect, animated, templated and
themed user interface you should think twice before choosing C. Java and .Net
are freaking good at database integration and memory allocation/dealocation.
Erlang is fantastic for multitasking and multithreading. Ruby on Rails or
Python on Django are much better to prototype a website nannying a database.

You already know the saying "if your only tool is an hammer..."

~~~
Delmania
Came to make a similar point. There's lot of love for C and belief it's some
superior language to modern ones. The reality is that C hasn't aged well as
hardware has evolved. It's not that good as parallel programming, which is
more prevalent considering most CPUs are multi-core. The ACM did a great
write-up on this,
[https://queue.acm.org/detail.cfm?id=3212479](https://queue.acm.org/detail.cfm?id=3212479)

The bottom line is to echo your first statement, C is a tool, good for some
tasks, not so good for others. Choose accordingly.

~~~
Koshkin
That's one of the good points about using C today. Unlike, say, Fortran (or
any other language with a high-level semantics) C may be hard for a compiler
to optimize for a modern CPU.

------
memn0nis
I too, love coding in C. I love how you can control almost everything. There's
nothing "mysterious" or abstracted away. Maybe I'm OCD, but I love
understanding exactly what is going on in my programs at all times

~~~
klingonopera

      #include <stdio.h>
      int main() {
          char a = 3;
          char b = 8;
          char result = a * b;
          printf("%u %u %u %u", sizeof(a), sizeof(b), sizeof(result), sizeof(a * b));
          return 0;
      }
    

PRINTOUT/x86-64: 1 1 1 4

Surprising results on x86, probably not on Arduino or embedded platforms.

The phenomenon is integer promotion in C.

EDIT: _" Char isn't an arithmetic type!"_ The same is true for short, too.

EDIT2: The consequences are: Might need to manually %= 0x100 instead of
relying on data type's inherent modulation in multi-operational arithmetic,
making rotation a necessarily pre-scan operation for LE/BE conversion instead
of a process-transparent operation and I had a third one... hm. Also, maybe
speed loss?

------
pgruenbacher
Shout out to
[zig]([https://github.com/ziglang/zig](https://github.com/ziglang/zig)) as a
modern alternative to C.

------
nabla9
If you want to write a library that can be used from many programming
languages in many operating systems, exotic processors and application
plugins, use C.

If you want to write a new programming language that does the same from the
get-go, make C as one of the compilation targets.

~~~
cosmic_quanta
Or export a C-API!

As other commenters have pointed out, use the right language for your use-
case. Then, I agree that exporting a C interface is best.

~~~
ncmncm
Exporting a C interface preserves some flexibility. That is often best.

------
actionowl
I DO TOO! Warts and all, the simplicity is really refreshing, especially in an
age of:

    
    
      "Make sure you have homebrew installed and then `curl http://goodluckbro.io/doomed | sh` then go read the Medium blog post to write your first hello world"
    
    

And don't get me wrong, I love and use whatever-shiny-lang-v2 too, I just
really like the simplicity of C (and yes I acknowledge that comes with the
ambiguity and undefined behavior too).

~~~
MadWombat
> Make sure you have homebrew installed and then `curl
> [http://goodluckbro.io/doomed](http://goodluckbro.io/doomed) | sh`

So... what is the equivalent of doing this in C?

* install C compiler (no, there is no install script, go google and install yourself and yes, the process is different on different platforms)

* read a blog post (possibly on medium) and write hello.c

* read another blog post to figure out compiler flags

* ./a.out

A bit less convenient, but not that different

~~~
sigjuice
On a new Mac, just type cc and it will install the command line developer
tools, which includes clang.

~~~
MadWombat
What if it is an older mac? What if it is a Windows machine? What if it is an
Ubuntu machine? Fedora machine? Arch Linux? FreeBSD? OpenBSD?

------
boramalper
> C is closest to the real

I think you can argue for both sides of the argument, but what is the
obsession for "the real"? Is the JavaScript running in your browser "less
real" than the x86 Assembly that is compiled _once again_ to Intel
Microcode?[1]

> When I saw a memory address outputted to stdout, I felt that I was as close
> to the hardware that I could be without literally opening up my machine and
> stroking my ram modules like a crazy person.

Sorry to shatter your enthusiasm but the memory address you are seeing is very
likely to be "virtual" and not physical thanks to paging: yet another damn
abstraction![2]

\-----

> "If I have seen further, it is by standing on the shoulders of giants."
> \--Isaac Newton

The Magic of Software: How I Learned to stop Worrying and Love Abstractions

[http://www.thecaucus.net/#/content/caucus/tech_blog/373](http://www.thecaucus.net/#/content/caucus/tech_blog/373)

\----

[1]:
[https://en.wikipedia.org/wiki/Intel_Microcode](https://en.wikipedia.org/wiki/Intel_Microcode)

[2]: [https://wiki.osdev.org/Paging](https://wiki.osdev.org/Paging)

~~~
ThrowawayR2
> " _I think you can argue for both sides of the argument, but what is the
> obsession for "the real"? Is the JavaScript running in your browser "less
> real" than the x86 Assembly that is compiled once again to Intel
> Microcode?_"

Because when the shiny abstractions and bloated frameworks layered like a
Guinness World Record stack of pancakes† that attempt to hide all the
underlying complexity of the hardware break down and leave you with a problem,
be it CPU, memory, or I/O, then anyone without an understanding of what's
going on at least at the assembly and architecture level will be up the
proverbial creek without a paddle. I've made good money bailing people out of
these kind of problems.

† About 60 cm or 2.5 feet apparently.
[https://www.eater.com/2012/2/21/6611971/this-is-
officially-t...](https://www.eater.com/2012/2/21/6611971/this-is-officially-
the-worlds-largest-stack-of-pancakes)

------
tyingq
I wish C had gone back and done a better implementation of strings that wasn't
null terminated. Or at least an implementation of plain buffers with a richer
libc for manipulating them.

I know you can find bolt ons, but they aren't terribly useful since no 3rd
party libraries use them. You have to resort to stuff like antirez's sds.

~~~
Koshkin
The choice is natural for C, due to the fact that it is the only way to
implement the string simply as an array of characters. (Doing anything else
would go against C's view of the world.)

~~~
tyingq
I suppose, but watching everyone mess about with a struct that has length and
a buffer, and a dearth of built-in functions to manipulate it, is...ugh.

~~~
59nadir
[https://github.com/antirez/sds](https://github.com/antirez/sds) is heaps
better than just using raw strings. Any language with a fat pointer variant
out of the box is going to do it better than C as well.

That's not to say that the rest of C is bad, but there's nothing to be gained
from defending C strings; they're bad and you should probably use an
alternative and that alternative should probably have a length and a buffer,
even better if you can find this out of the box in your language that
otherwise works a lot like C.

------
pjmlp
> C is closest to the real

Nope, plenty of alternatives have existed throughout the history of computing

[https://en.m.wikipedia.org/wiki/System_programming_language](https://en.m.wikipedia.org/wiki/System_programming_language)

> C as the Ur-language

Not really, ESPOL came up in 1961, while C only became a thing in 1969.

~~~
saagarjha
They said is, not _was_.

------
FpUser
I like C myself. I wish though they would extend it with some features similar
those available in Delphi/FreePascal language. This will still leave complete
low level control and have amazing compile speed and eliminate much need for
C++. C++ is heavy beast and while I a have to use it every once in a while I
do not particularly enjoy compile time and some other things.

Zig made some moves to that direction but not yet enough and is not really
ready for production anyways. Also I did some experiments and initial
impression was good but it failed to compile used libs written in C even
though it claims to be a C compiler as well.

~~~
dnautics
Zig is fantastic, but it's still early days. If it doesn't compile all c code
ever I wouldn't mind taking c code, wrapping it into a .a and dropping it into
a zig program until you can untangle it.

------
unlinked_dll
C has aged liked fine wine with the cork out.

------
dcchambers
We used C99 in some of my classes at college, despite attending University
from 2010~2017. I liked C a lot - working that 'close' to the hardware and the
OS really gives you an appreciation for how things work. These days I've moved
on to things like Ruby, Python, and Go...but I'd like to give it another try
with C18 to see what modern C looks like.

~~~
saagarjha
C has changed little compared to languages such as C++. There’s a couple of
nice additions, but it’s not a completely new language.

~~~
Koshkin
This might be an indication of the language being close to perfect as it is.

~~~
ncmncm
Or, more likely, that the action has moved elsewhere.

There are lots of small towns still emptying out. The people who remain are
more likely to say they like them.

------
russtrotter
For as much as it seems comical, I enjoy languages like Java and C# so much
because while I cut my teeth on lots of C (then C++) from the early 90's
onward, once the web began to drive so much development, you needed to be able
to sling strings (with first-class Unicode support) around.

When I was learning C++, a popular idiom to employ was to use it as "a better
C". Don't make your own classes, but leverage good "string" impls, templates,
namespaces, declaration semantics, RAII, etc, basically stuff that just your
C... better.

I can appreciate good nostalgia, but I can't help but think i'd be pining for
some of those betternesses that came along later.

------
continuational
Hardware has evolved a lot since C was made, but is there a way to actually
make use of this that isn't available in C? It seems like modern CPUs do an
awful lot to support programming like it's still the 1970'ies.

------
swiley
I’ve been messing with yasm lately and I think I might like that more. I’ve
written a lot of C (including a small browser) and definitely enjoy it
(especially parsing, which is probably not great.) The more I’ve written and
read though the more I’ve realized how much it really does abstract and that
kind of sucks the joy out of it for me.

For actually getting things done there’s go and python of course.

------
GuB-42
That song sums it up nicely
[https://youtu.be/tas0O586t80](https://youtu.be/tas0O586t80)

------
skocznymroczny
I really like C too, although I wish it was slightly higher level. Proper
strings, built-in hashmaps, few other small improvements.

------
userbinator
I do too, mainly C + Win32. Besides Asm (which I use a little bit of too),
it's the only way I've found to write applications which are a single tiny
executable with no dependencies nor installation, and will work from Win95
upwards (and likely WINE as well). I'm a little surprised that the author
didn't mention efficiency.

------
dleslie
The Ur-Language is probably verilog, or at least the raw instructions of the
platform; and C isn't much closer to those instructions than many of its
alternatives, thanks to optimizing compilers and the differences between the
true form of the computational model of the platform and the imaginary one
that the C standard reflects.

------
mobilemidget
"I hope I am able to explain some of the psychological appeal of lower level
programming in C."

Felt like I finished reading the intro here, only to realise I scrolled to the
bottom with only 2 paragraphs to read more. Guess I was expecting a bigger
read, perhaps with some code references, as it was interesting :)

------
frou_dh
One of these decades we'll get over compulsively raking the embers of Bell
Labs

~~~
Koshkin
We should be grateful for not discussing PL/I (and Multics) here.

~~~
pjmlp
From security point of view it would have been an improvement.

------
app4soft
Q: _“Do you prefer C or C++?”_

A: _“C tbh”_ [0]

[0]
[https://twitter.com/elonmusk/status/1211786326670036992](https://twitter.com/elonmusk/status/1211786326670036992)

------
m0zg
I love coding in a "C+" subset of C++, meaning minimal use of OOP, and maximal
use of RAII, smart pointers, and STL. It's hard to beat C++ when it's scoped
down like this.

~~~
saagarjha
There’s still a couple finicky memory issues, unfortunately.

------
pxi
C with valgrind and other analysis tools (clang scan-build/cppcheck etc) make
for a powerful combination. There should be a dinky acronym. But, never gonna
happen in my workplace.

------
bitwize
I love coding in C, too, but the writing is on the wall. C is being put out to
pasture in favor of Rust.

------
TheRealPomax
The arguments sound like they should be about Rust at this point in history.

------
BadThink6655321
I love coding.

~~~
Koshkin
Indeed, there is something very satisfying in constructing things that work
(no matter the language).

------
master_yoda_1
Me too

------
SandPhoenix
I also took Jae’s cs3157, and I loved every second of it! So glad to see a
fellow AP alum on HN ;)

------
adamnemecek
There is little content in this article.

~~~
saagarjha
It’s reasonably short and it gets its point across. What where you looking
for, specifically?

------
sys_64738
If it had native support for JSON and supported GO like co-routines then a lot
of its utility would be applicable to other scenarios that use fancy
languages. This is what I always find surprising, C’ syntax is robust so add
these missing gaps to the current language and weall win.

~~~
krapp
>C’ syntax is robust so add these missing gaps to the current language and
weall win.

If every feature everyone wanted was added to C, it would just turn into a
bloated mess like C++, or like PHP, with tons of bad decisions baked into the
language that can't really be avoided. Features like this belong in libraries
- the stdlib should remain as minimalistic as possible.

~~~
saalweachter
If everything everyone wanted was added to C, it _would_ be C++. :-)

The C++ evolution is essentially an iterative cycle where people hackily
create new language features with insane preprocessor and template hackery,
and then the Standards Committee comes in and takes the most popular hacks and
tries to turn them into real language features. And then people create new
hacks on top of that.

C says, you want object oriented polymorphism, you've got structs and function
pointers, what more do you need?

~~~
krapp
>C says, you want object oriented polymorphism, you've got structs and
function pointers, what more do you need?

No, C says your first mistake was wanting object oriented polymorphism. Your
second mistake will be implementing it.

~~~
saagarjha
Most large C projects end up succumbing to something like this at some point.

~~~
cardiffspaceman
I participated in the coding of several arcade games in Z80 assembler in the
late 80s. Not "a" large project but a collection of small ones.

These programs, by the time I came aboard and started sharing code with those
to make the games I was involved in, had also "succumbed" to this force.

The shared bedrock of all these games was code that used certain Z80 registers
as pointers to take advantage of instructions that read or wrote fixed offsets
from those pointers. The first byte of the record pointed at by the pointer
was the type and the next 4 bytes were the position. The rest of the record
depended on the type.

That was us doing the best we could with 8-bit processors when our competition
was using 68000.

------
TallGuyShort
I used to enjoy C. If you enjoy C for these reasons, you may enjoy Go and the
benefits of living in this millenium. It feels very minimal with a lot of
control. Similar heritage. Definitely feels like C a lot of the time. And you
get to use the & operator if that's your thing. It's got cleverness to infer
types if you want, but you can specify types and a lot of the cleverness is at
compile-time: still very light-weight to run. And it's got a better standard
library which makes it a lot more fun to throw something together quickly.

~~~
cyphar
When it comes to simplicity, Go is definitely quite similar to C in many
respects (and not all of them good). After I started working with Rust for a
little bit, I think it better matches the feeling I get with C when it comes
to being confident what your code will do when compiled. To be fair, there is
still a fair amount of magic you have to get used to.

But the main issues that I've had with Go is when the Go runtime is causing
your code to very much not act like a C program. To be fair, I work on
container runtimes and similarly "lower level" tooling so this is probably not
an experience that everyone else has.

------
raverbashing
C is simple. But not ergonomic. Or safe. It's bad to the point of being almost
broken.

"Oh but it's powerful", yes, a chainsaw is powerful but even that has an
emergency brake. C has none of that.

(Not to mention compilers that take "undefined behaviour" as a code word for
"bamboozle time")

~~~
andrepd
Undefined behaviour is a tool to generate faster code, by shifting some burden
of correctness to the programmer. For example, accessing past the array size
is undefined behaviour. This lets the compiler skip the bounds check, an
expensive check+branch on each array access. The trade-off is that now the
programmer has to ensure correctness manually (or use an alternative interface
that wraps a bounds check) else bad (TM) things will happen.

~~~
AaronFriel
Despite this, tools will often not warn of undefined behavior or these
premature optimizations. Quite the opposite, the compilers are almost user
hostile for trying to ensure safety and correctness. I cannot see into the
minds of the GCC and LLVM teams responsible for the C frontends, but to the
extent that you'd think the compiler should do anything here, they seem to
think it's largely not their job to improve safety or correctness. That's the
job of static analysis tools (which can cost a lot of money), runtime analysis
tools (which can cost a lot of time in writing tests that exercise all code
paths), and fuzzers (yet more time).

Punting all of this extremely important reasoning to "the programmer" is
ridiculous when it's been proven over and over again that even incredibly
smart C programmers get this wrong.

~~~
saagarjha
Compilers could certainly do a better job when performing optimizations that
“abuse” undefined behavior, but in general this isn’t a trivial problem. The
way optimization passes are set up can often make it difficult to generate
warnings that have a low false-positive rate.

~~~
AaronFriel
Do you think it might be a problem that undefined behavior optimizations
and/or pattern matches are so prolific in C that reducing the "false-positive
rate" of ... actually bad undefined behavior (?) would be the hard thing?

Why not disallow undefined behavior and instead have the compiler emit: "Hey,
this branch is never taken, if you want us not to compile it remove the dead
code". Seems like that's exactly the same sort of putting the onus on _the
programmer_ that andrepd mentioned, but without the side effect of millions of
device drivers are zero day remote code execution vulnerabilities waiting to
happen?

~~~
cyphar
The array access example shows why this isn't generally possible -- the
programmer hasn't written any bounds-checking code or assertions (they just
wrote x[y]). Should the compiler emit bounds-checks? If yes, your code is now
slower but there isn't an easy way to disable this "feature" (if x is heap-
allocated or passed as a pointers, how will the compiler know what kind of
bounds check is sufficient?). If no, you have undefined behaviour and crashes.

Even Rust made a mistake with restricting undefined behaviour -- they
accidentally allowed creating references to fields in packed structures
(resulting in possibly unaligned references which is undefined behaviour in
Rust) in safe code. These days you get a _warning_ telling you to use unsafe
or copy the value, but they have yet to make it an error. For comparison, C
also gives you a warning for the same problem (or an error with -Werror).

I also want to point out that modern compilers have different sanitisers
(-fsanitize=...) which cause your program to abort() on memory unsafety or
other violations. It's effectively a better valgrind which is compiled into
your program.

~~~
AaronFriel
I would argue that most of the time, yes, the compiler should emit bounds
checks and only in performance sensitive code should they be removed (or where
the compiler can prove the bounds check holds for a range).

I don't know how anyone who has studied the history of vulnerabilities and
security issues and code would think this is a hard tradeoff:

> If yes, your code is now slower but there isn't an easy way to disable this
> "feature" (if x is heap-allocated or passed as a pointers, how will the
> compiler know what kind of bounds check is sufficient?).

> If no, you have undefined behaviour and crashes.

The latter choice has resulted in how many millions or billions in losses,
perhaps even lives lost?

~~~
cyphar
The problem is that if you want to permit users to do things like dereference
pointers and do pointer arithmetic (a requirement for a C-like language) there
is no way for the compiler to ensure (even at runtime, because the pointer
might not be to memory managed by the language) that the pointer dereference
is valid.

Okay, maybe you can add a small overhead to array accesses (which is what Rust
did), but you can't stop undefined behaviour entirely. Rust has plenty of
undefined behaviour (most can only be triggered through unsafe -- which is the
whole argument behind the language -- but as I mentioned there are some cases
where even they made a mistake and made an operation safe when it should've
have been).

~~~
AaronFriel
The benefit of "unsafe" blocks is that the surface area to audit is
dramatically reduced, so that yes, if you want high performance pointer
mangling and arithmetic, you can do it, but your code will require more
attention.

In C/C++, undefined behavior and unsafe operations abound and it is nigh
impossible to isolate what is "important" to review from a security
standpoint.

