
Principles for C programming - ddevault
https://drewdevault.com/2017/03/15/How-I-learned-to-stop-worrying-and-love-C.html
======
cperciva
_Avoid magic. Do not use macros._

Disagree. Use magic, especially macros, _in ways such that your code becomes
easier, not harder, to understand_.

A few examples from my own code:

1\. My "elastic arrays"
([https://github.com/Tarsnap/libcperciva/blob/master/datastruc...](https://github.com/Tarsnap/libcperciva/blob/master/datastruct/elasticarray.h)
and .c) allow me to write

    
    
        ELASTICARRAY_DECL(STRLIST, strlist, const char *);
    

and get a data structure STRLIST which contains an arbitrary number of strings
and functions strlist_init, strlist_append, strlist_get, strlist_free, etc.
for accessing the array. Compared to the non-macro approach of keeping track
of the array size and resizing as needed, this makes code vastly simpler. (Of
course, this sort of data structure is built into most non-C languages
already.)

2\. My "magic getopt"
([https://github.com/Tarsnap/libcperciva/commit/53d00e5bd0478f...](https://github.com/Tarsnap/libcperciva/commit/53d00e5bd0478fcc89bc056d192a476fb89277a4))
allows me to something which looks and behaves just like a standard UNIX
getopt loop, except with support for --long options. Yes, the implementation
is mildly insane (and needs to work around a bug in clang!), but it allows for
code which is vastly simpler than other getopt-with-long-options alternatives.

3\. My "cpu features support" framework
([https://github.com/Tarsnap/libcperciva/blob/master/cpusuppor...](https://github.com/Tarsnap/libcperciva/blob/master/cpusupport/cpusupport.h))
makes use of both macros and some tricky edge cases of C object linkage rules,
but makes it trivial for me to add support for new CPU features.

4\. Soon to be released, the PARSENUM macro (WIP:
[https://github.com/Tarsnap/libcperciva/blob/parsenum-
additio...](https://github.com/Tarsnap/libcperciva/blob/parsenum-
additions/util/parsenum.h)) which allows me to write

    
    
        PARSENUM(&n, "1234");
        PARSENUM(&x, "123.456");
        PARSENUM(&s, "123", 0, 1000);
    

where the first argument is a pointer to a variable of any integer or
floating-point type to which is assigned the numeric value of the string in
the second argument; for floating-point values and unsigned integers, the two-
argument form range-checks the value against the bounds of the type, while the
four-argument form range-checks against the provided bounds. (Basically, this
is strtonum on steroids.)

In all of these cases, _you will never need to understand how these macros
work_. Instead, you can simply treat them as language extensions which allow
you to write cleaner and simpler code.

~~~
Sir_Cmpwn
>1

I'm conflicted about this one. I choose not to try and emulate generics with
macros because adding language features with macros is a terrible idea. On the
other hand, I recognize the problem with void*. It's a matter for debate but I
definitely fall on the "don't use macros for this" side.

>2

This is a case where I would rather write more code than rely on magic. You
have to have this awful hacky opaque implementation in exchange for a trivial
improvement in ergonimics. No thanks.

>3

I mean, just look at this code. Or better yet, have someone else look at it.
This is totally unreadable and unmaintainable, all for a marginal ergnomics
improvement.

>4

Just use strtol or strtof. Do you really run into this that often?

All of these are demonstrating exactly the problem I have with a lot of C
authors. You build these esoteric systems that use heaps of unmaintainable,
unreadable code to provide marginal gains elsewhere.

~~~
cperciva
_Just use strtol or strtof_

Which of these is more likely to have bugs?

    
    
        int i;
        
        if (PARSENUM(&i, s, 0, 1000))
            err("Invalid input: %s", s);
    

or

    
    
        int i;
        long l; // need a temporary long to avoid overflow
        char * ep;
        
        errno = 0;
        l = strtol(s, &ep, 0);
        if ((ep == s) || (*ep = '\0'))  // make sure we parsed a number and don't have trailing garbage
            errno = EINVAL;
        if ((l < 0) || (l > 1000))
            errno = ERANGE;
        if (errno)
            err("Invalid input: %s", s);
        i = l;
    

_You build these esoteric systems that use heaps of unmaintainable, unreadable
code_

Not at all. The point is to have self-contained routines which Just Work in
order to ensure that the rest of the code is easier to read and maintain.

~~~
Sir_Cmpwn
To be honest I would just have your library offer a function that does it your
fancy way. I agree that there could be better integer parsing functions, I
disagree that they should be macros.

>Not at all. The point is to have self-contained routines which Just Work in
order to ensure that the rest of the code is easier to read and maintain.

Self-contained routines that are completely unmaintainable and unintelligible
to anyone but you, though. Not worth it.

~~~
cperciva
_To be honest I would just have your library offer a function that does it
your fancy way._

It's not possible to have a single function do this. Not possible to have any
finite number of functions do this if, like me, you want to support all
floating-point and integer types.

 _Self-contained routines that are completely unmaintainable and
unintelligible to anyone but you, though._

All of my macros are intelligible to anyone who understands the C
preprocessor.

More to the point, why do they _need_ to be maintainable? When was the last
time you maintained the strtof function in your C library?

~~~
mfukar
> More to the point, why do they need to be maintainable? When was the last
> time you maintained the strtof function in your C library?

A few years ago.
[https://sourceware.org/bugzilla/show_bug.cgi?id=15744](https://sourceware.org/bugzilla/show_bug.cgi?id=15744)

Acting like you can get anything done right in C simply because it's self-
contained is proven wrong every day. It's good practice, yes, but doesn't
magically (we like this word now) make us immune to error. Everything needs to
be maintainable, even if it is proven to be correct, for the simple reason
that we can't just replace broken pieces of code with the same simplicity we
can replace a broken fridge.

Also, if there's anything we know about code, is that we are constantly trying
to invent new ways to expose it to a new interface - thereby breaking it.

~~~
camgunz
> we can't just replace broken pieces of code with the same simplicity we can
> replace a broken fridge.

Sure we can. Recompile glibc, dynamic linking, pow.

I wouldn't say that bug you referenced is a strike against C; it could happen
in any language that was locale-aware and parsing floats.

For what it's worth, these macros in libcperciva are perfectly readable and
maintainable:

    
    
      #define ELASTICARRAY_DECL(type, prefix, rectype)			\
      	static inline struct prefix##_struct *				\
      	prefix##_init(size_t nrec)					\
      	{								\
      		struct elasticarray * EA;				\
      									\
      		EA = elasticarray_init(nrec, sizeof(rectype));		\
      		return ((struct prefix##_struct *)EA);			\
      	}								\
      ...
    

The advice should really be "be careful, it's really easy to write shitty
macros, so don't".

~~~
mfukar
> Sure we can. Recompile glibc, dynamic linking, pow.

Oh, so I have to maintain glibc. That's the alternative, we are in complete
agreement.

> I wouldn't say that bug you referenced is a strike against C; it could
> happen in any language that was locale-aware and parsing floats.

I was responding to a very specific (and pointed) question.

> For what it's worth, these macros in libcperciva are perfectly readable and
> maintainable:

I'm not contesting that, they might be. The one you list looks fine at a
glance, but that doesn't prove anything.

------
CapacitorSet
>Do not use macros. Do not use a typedef to hide a pointer or avoid writing
“struct”. Avoid writing complex abstractions. Keep your build system simple
and transparent. Don’t use stupid hacky crap just because it’s a cool way of
solving the problem.

Heh, good luck avoiding the use of macros in sufficiently complex projects -
sometimes C just can't use some control structures in an elegant manner
without using macros or custom abstractions.

~~~
huhtenberg
> _Do not use macros._

Sir, yes, sir. I will throw away "offsetof" and "container_of" right now, just
give me a moment.

> _Do not use a typedef to hide a pointer or avoid writing “struct”_

Somewhat agree with the former, but completely disagree with the latter. If
there's a single thing that is not right with C is its excessive verbosity in
places where none is needed.

Not typedef'ing your structs forces you to use extra 7 characters per type
mention for no clear benefit. To put it differently - if NOT having "struct"
in front of a type name has _any_ effect on readability/maintainability of
your code, then there are deeper problems with your coding style that won't be
solved by dragging "struct" around.

~~~
ddevault
>Not typedef'ing your structs forces you to use extra 7 characters per type
mention for no clear benefit. To put it differently - if NOT having "struct"
in front of a type name has any effect on readability/maintainability of your
code, then there are deeper problems with your coding style that won't be
solved by dragging "struct" around.

The benefit is to readability. You should treat structs differently from
scalars, and the code should make the distinction apparent. You should not
generally, for example, pass structs by value. This is just laziness.

~~~
huhtenberg
Not sure I follow.

> scalars

So you would typedef the scalars then? If you don't, then scalars will be the
built-in types (which you'd presumably know well) and then all other type
names will be typedef'ed structs/unions, still making it trivial to recognize
them as such.

~~~
Sir_Cmpwn
Yes, I think typedefing scalars is fine. Typedefs are useful for abstracting
the underlying storage mechanism for a scalar (so you can i.e. change it on
different archictures or in a future release without breakage), not for saving
yourself 6 characters of typing.

~~~
eps
Typedefs are useful for creating short-hand names for otherwise long or
complicated type definitions. Saving 7 (6 for 'struct' \+ space) characters is
as good use for typedef as any other.

~~~
falcolas
You only have to write each set of seven characters once; yes, typing will
require a bit more effort. However, we shouldn't optimize code for the ease of
writing, we should be optimizing for the ease of reading. Write once, read
many.

`struct foo` is a bit more instructive when understanding code than `foo`; at
worst, they read the same to someone familiar to the codebase, at best they
prevent the need to flip back and forth to the type definitions.

~~~
dTal
>Typedefs are useful for abstracting the underlying storage mechanism for a
scalar (so you can i.e. change it on different archictures or in a future
release without breakage), not for saving yourself 6 characters of typing.

------
keithnz
1) Learn Compiler Design

2) Write a Compiler for a better language

3) In new language, write a compiler for your new language

4) Retire from C programming, occasionally come to Hacker News to reminisce
about C programming and ways to avoid shooting yourself in the foot

~~~
zild3d
or

1) Learn Embedded Design

2) Stay in C for another few decades

~~~
nonsince
I'm in embedded, but I have a fair measure of autonomy so I've got some Rust
code on our device. If nothing else, the cross-compilation story for Rust is
absolutely _beautiful_. Just stick the toolchain path in a config file
somewhere.

------
pif
TL;DR: move on, nothing serious in this article. Just barking useless advice
and spitting insulting nonsense.

~~~
andrepd
Just like your worthless comment. The irony.

~~~
pif
I posted it in hope to prevent someone, somewhere, to waste some of his time.

------
arkj
>>Avoid magic. Do not use macros

What a put off!!!

If you are programming in C in the 21st century then you better know what you
are doing. And this whole advice is for dilettantes (no offense).

C is no longer a choice language to demonstrate high level programming
principles (not that you can't do it but it's not for the lazy), there's a
host of other languages that do that better. But if you are interested to
reach close to the machine (eg: you program needs a direct view of memory)
then C is 'the' choice even today.

Look at the kernel list.h [0], it's a beautiful piece of code, and how
concisely it uses macros. So the real advice to those starting out in C is to
be bold and get immersed in all the things that people say you should not do
and then let simplicity emerge.

In other words, 'simplicity' of the novice and of the experienced share the
same word but are two different concepts from two different points of view.

[0][https://github.com/torvalds/linux/blob/master/include/linux/...](https://github.com/torvalds/linux/blob/master/include/linux/list.h)

~~~
camtarn
I'm coding in C89 because I'm writing software for industrial embedded
controllers. Future maintainers of this software are more likely to be
electronic engineers than programmers, so "keep it simple and don't use magic"
is actually extremely good advice.

That said, my code does use a couple of macros, but only to do stuff that
would be very long-winded and/or impossible without, and using malloc at all
is very much frowned upon: storage should either be declared ahead of time in
special .var files, or on the stack.

~~~
axoltl
Yeah, in general I agree with the article, the 'malloc all your memory' part
is something I disagree with heavily. Outside of the fact that many malloc and
calloc implementations don't particularly deal well with integer overflow[0],
it introduces non-determinism into your code. MISRA C even forbids the use of
dynamic memory allocation.

In fact, the environments where C shines oftentimes won't even have a dynamic
allocator...

[0] Try allocating ((size_t)-1) bytes. Quite a few implementations will give
you a pointer back! They'll add some space for a header, or round up the size
to the nearest n-byte boundary. This problem's compounded by the fact people
assume passing unsanitized input to malloc effectively sanitizes it.

------
jabot
> GNU is a blight on this Earth, do not let it infect your code.

Can someone explain this sentiment to me? I know about licensing and
philosophical criticisms, but are there any _technical_ faults?

~~~
mhd
It's "All the world's a VAX" in its new form, where you depend on some
language/library feature that's actually not in the standard (IIRC alloca and
preprocessor extensions are common culprits).

And then suddenly you're on a different platform and discover that you can't
rely on that -- pretty bad if it's in a central part of your system (like
trampolining functions for your toy lisp).

On the other hand, you might run into the same issues if the standard support
is sub par. People used to declare K&R-style functions for ages after ANSI was
passed, and I wouldn't bet a central part of my system on C99.

~~~
jabot
So in your opinion the problem is "only" one of portability to non-gnu
systems?

I thought the author also implied that GNU was technologically inferior and/or
problematic. That would interest me...

~~~
mhd
Probably the ubiquitous "bloat" issue some die-hard C-heads have. But that's
his prerogative, I was just pointing out that the specific context points
towards portability issues.

~~~
duck2
There is quite a lot of GNU code written in C.

However, since gnu is a political umbrella project which does not necessarily
value code quality, we see a lot of unnecessary complexity arising from the
focus on creating working "free" software and not minding the quality much.

Anyone who ever touched GNU autotools will confirm that the bloat issue is
real. No need to be a diehard C head.

------
buserror
I disagree with quite a few of them. The title should be "Principle for C
programming ON UNUX BASED SYSTEMS". C programming is quite a bit wider that
this, and some of the typical points here are no-no if you want to write
/portable/ C.

For example, "Do not use fixed size buffers". It's all very fine, but 1) it
can be exploited _as well_ if someone managed to fudge the size you are going
to allocate, and 2) on some platform, you don't have/want malloc(). So it's a
lot better to have a fixed buffer _and check the sizes carefully_ before
copying into it.

Another one I dislike (but it's personal preference) is the 'use struct for
pointers to structs' \-- well, nope, I don't like that, it's unnecessarily
heavy. I typedef my structs all the time, and call them something_t, and *
something_p. It's easier to rework, rename, search for and it's quicker to
type so makes the source code lighter to read. I _know_ it's not popular, and
for example the kernel guidelines agree with you, but I don't.

As for "no circumstances should you ever use gcc extensions or glibc
extensions" well sorry, I also disagree here. I love the 'case X..Y:' syntax
for example and it's been around for about a million years. It's not because
the C standards prefer adding idiotic syntax instead of _useful ones_ like
this that I'm going to stick along and limp when there is a perfectly nice,
clear and very readable alternative.

Another one I _love_ but can't use are the sub-functions. Now _what also_
would have been a lovely extension if the runtime had been perfected a bit,
but it was never 'finished'. Speak of easier code to read when your qsort()
callback is listed /just above/ the call to qsort().

Another extension is of course the __builtins that you actually do need on
modern systems. Like memory barriers, compare and swaps, ffs, popcount and so
on. Of _course_ I can have an explicit function to do it (in the case of the
last 2), but that's the sort of things that _ought_ to be in the C library
anyway. So I'll use these, thanks.

As far as the rest of the article about the process, your code reviewers and
so on, in many places and on many projects (open source ones are a case in
point) you don't have the freedom/time to do that. The rule is ' do as best as
you can' \-- and that _ought_ to do it in many cases.

~~~
awbrown
> 'I typedef my structs all the time, and call them something_t, and *
> something_p’

I wish people would stop perpetuating this particular naming convention. Its
in violation of POSIX which specifically reserves the entire *_t ‘namespace’.
Obviously its fine if this is done on a system or environment for which this
is irrelevant, but its best avoided otherwise.

~~~
mbel
The reality is that if you are creating a library you probably should prefix
your types and functions anyway. And rely on the prefix to minimize collision
probability. So it doesn't really matter if you put _t and the end of your
type aliases. You will probably not get the collisions anyway. Unless POSIX is
going to suddenly introduce mylib_array_t or something.

~~~
jbb123
No, but your compiler MIGHT decide in a future release that it's a whole lot
faster to ignore the header files for standards types and definitions and just
copy a pre parsed version of the struct into the symbol table when the header
is included. It might look at the _t and decide nope, I don't have a
definition for this so it's an error, despite your own definitions.

This probably won't happen. But if it does you don't have any grounds for
complaint really.

~~~
mbel
The compiler to do that would also need to drop C standard compatibility
(section 7.1.3 of C99). Which is probably a good reason to complain and to
just stop using that version of this purely theoretical compiler.

------
ianai
Feels too heavy on "donts." I imagine the motivation the desire to be heard
and understood. But I also need something simple, like "keep it simple,
stupid" or "first have a working product that anyone could read". Positivity
>> negativity.

~~~
dovdovdov
I guess there are 'donts' because people need help with the keep it simple
part, they are quite comfortable with the stupid part.

~~~
ianai
"nobody's grading your code by how many abstractions and topics from a
textbook it employs."

~~~
AstralStorm
That said, people sometimes ate grading your code based on performance. Good
algorithms often win over simplistic ones.

~~~
makapuf
I'd say when you have big n, smart algorithms O(n) are better. But most of the
time you have small n. better to optimize just the real big N in your program,
for what you profiled, than using complex and "optimized" algos when N<1000.

(I cannot retrieve the article this was taken from)

~~~
adrianN
"It's okay for my n!"
[http://accidentallyquadratic.tumblr.com/](http://accidentallyquadratic.tumblr.com/)

------
MrUnderhill
I've recently been reading Scott Meyers' Effective (Modern) C++ books. They
are fantastic - can anyone recommend something similar for C? I.e. books that
assume you're familiar with the language, but explains pitfalls and best
practices in a practical way.

~~~
fnord123
Deep C Secrets.

~~~
krylon
That book is great, very in-depth, yet fun to read.

One caveat, though, this book is from the mid-nineties, so some parts are a
little dated. Still, very much worth reading.

------
rumcajz
One thing that comes to mind is that young people today are weaned on oo
languages and may find it hard to adopt the good old-fashioned non-oo style
typical for C.

For example, intrusive data structures may seem like an anti-pattern to oo
programmers, while they feel perfectly ok for a C programmer. On the contrary,
C programmer may find non-intrusive data structures to be an anti-pattern as
they require extra memory allocations.

------
rbehrends
The problem with all this advice is that C lacks so many amenities of modern
programming languages (no module system, no opaque types, no automatic memory
management or RAII, no closures, no parametric polymorphism, no subtyping)
that working around these limitations is bound to violate some of it.

The same strict rules that you can afford to adhere to in other languages do
not work for C: with C, the often delicate balance of tradeoffs can tip either
way.

> Do not use a typedef to hide a pointer or avoid writing “struct”.

This really seems to fly in the face of the idea of information hiding, at
least when listed as an absolute. The client of a module does not need to know
whether (say) a handle is an int or a pointer.

> Do not use macros.

> Never put code into a header. Never use the inline keyword.

This just doesn't strike me as good advice. You're creating potentially costly
abstractions. For example:

    
    
      for (foo_init(&foo); !foo_done(&foo); foo_next(&foo)) {
        ...
      }
      

Without macros or static inline functions, you're creating overhead. Overhead
that other languages with proper module systems can avoid, but not C, so you
have to work around it. Sometimes it's not an issue, sometimes this is code
that you want to use in a hot loop. By not having low-overhead abstractions,
you're encouraging manual inlining or hacking around their lack when writing
performance-critical code (and if you aren't writing performance-critical
code, why are you using C)?

Macros are also the only alternative that you have in C to compensate for the
lack of closures. They aren't closures, and they aren't even good macros, but
cover some of the same use cases.

> Do not use fixed size buffers - always calculate how much space you’ll need
> and allocate it.

This is not exactly wrong, but it only talks about half of the problem. The
alternative to fixed size buffers are manual memory management or alloca().
Manual memory management is also error-prone and alloca() can blow up the
stack. It's not that people necessarily think that fixed size buffers are
good: they're choosing between various pain points. Fixed size buffers with a
generous upper limit that is properly enforced can make for a perfectly viable
trade-off.

> Keep your build system simple and transparent.

This is a bit vague. What's "simple and transparent"? At some point, you'll
have to deal with things like how to figure out dependencies, for example.
Preprocessors and code generators are common in C projects, often to reduce
common C pain points.

------
nspassov
These points may be especially important for C programming, but most of them
really apply to programming in any language.

------
GuB-42
I mostly agree, however I don't like the idea of making code easy for novices
to understand as a primary goal. That's because may imply not using some
powerful constructs out of fear they may not be well understood by beginners.
I think it is better to imagine that the next person reading your code will be
better than you and that he is going to judge you. So don't hold back, use
your clever tricks, but make sure you use them correctly otherwise you will
look like a fool.

Also, never say never. All non-deprecated features of the language can be used
: goto, inline, macros, you name it. You just need to know how to use them
wisely.

------
JetSetWilly
> Use only standard features. Do not assume the platform is Linux

I'll decide what platform I am targeting, thank you. I don't feel any
obligation to support obscure OSes if I am just targeting linux. I might as
well use useful linux-specific and GNU userland features, they are helpful.

~~~
mfukar
"Do not assume" doesn't really mean or imply "do not target", so stop being
defensive. It's good advice.

~~~
ckastner
Targeting expresses expectations about an environment. "Do not assume" would
be contradictory to those explicit expectations.

~~~
mfukar
And since we can follow that thought process, we are smart enough to infer the
meaning of the advice given ("do not assume") in the proper context, can we
not?

It's obvious to me we both can, so why the hell are you wasting our time with
useless pedantry?

~~~
ckastner
Why are you inferring meaning when the article's author used two paragraphs
and numerous examples to illustrate his point unambiguously?

The article is clearly heavy on the absolutes (eg: "Under no circumstances
should you ever use"), and the grandparent comment critized this, and provided
a good counterexample.

But you, ironically, chose to assume what the author meant by "do not assume".

~~~
mfukar
OK, _now_ I know you're trolling.

------
borplk
All sounds good in theory but in practice in a large enough project many of
the suggestions are not practical.

Recently I was looking at rsyslog's source code.

Look at this "simple" file for example,
[https://github.com/rsyslog/rsyslog/blob/master/plugins/omstd...](https://github.com/rsyslog/rsyslog/blob/master/plugins/omstdout/omstdout.c)

It's an output module for rsyslog that logs to stdout.

I wanted to gouge my eyes out.

~~~
Sir_Cmpwn
This code fails to follow many of my suggestions.

\- Liberal use of macros

\- Horribly unreadable coding style

\- Needless use of compiler extensions

\- Very poorly organized code

This is awful code because the authors are morons, not because the language is
bad. They could stand to read this blog post.

~~~
ktRolster
I did find this line of code quite entertaining:

    
    
         if((r = write(1, toWrite, len)) != (int) len) { /* 1 is stdout! */
    

If you're going to go to the effort to add the comment, why not just use
stdout instead of 1?

~~~
makapuf
indeed, it's just a naive example but wouldn't something like

    
    
        int written = write(STDOUT_FILENO, toWrite, len);
        if (written != len) 
            { ...
    

be simpler ?

------
fish2000
__Do not use magic. __— Strongly agree.

 __Do not use macros. __— Absolutely I agree: I involuntarily grimace whenever
I look at and /or things like Boost MPL, or the wartier corners of the Python
C-API’s underbelly, etc. I only use macros as straight-up batched ⌘-C-⌘-V:

    
    
        #define DECLARE_IT(type, value) extern const type{ value }
        DECLARE_IT(int, 0);
        DECLARE_IT(int, 1);
        DECLARE_IT(float, 0.0f); // etc
        #undef DECLARE_IT
    

__Do not use typedefs to hide pointers […] __— I cannot stand it when people
do this. That asterisk is as syntactically valuable to you, the programmer, as
it is essential to your program’s function. If the standard library can slap
asterisks on file and directory handles than so can you (and by “you” I
specifically include whoever wrote the `gzip` API among other things).

 __[…] or to avoid writing “struct” __— Huh, actually I feel the opposite, I
think all those “struct” identifiers are clutterific, much like excessive
“typename” id’s in C++ template declarations. But so aside from the points
where I totally disagree with the author, I absolutely feel the same way 100%.

------
hashmal
interesting points. the maintainability part is freaking important but i guess
you truly realize it only when you got bitten by it several times. some other
details i don't necessarily agree with, like buffer sizes if you program for
embedded devices or if you have real-time constraints.

------
Beltiras
Made me think about the line in Full Metal Jacket: "C is my programming
language. There are many like it but this one is mine."
[https://www.youtube.com/watch?v=nkGIxGdZoYY](https://www.youtube.com/watch?v=nkGIxGdZoYY)

------
ziikutv
Thanks for the read. Very educational.

I would suggest increasing the font-weight of your bolded text. I could not
differentiate the two when skimming over the article; only when I read very
carefully did I notice that you had somethings bolded.

------
camgunz
I'll wade in a little.

> Don't use macros.

> Don't use the inline keyword

> Never put code into a header

I disagree with these. It's often critical to avoid a function call in hot
paths, and if you don't use macros or inline you have to resort to copy/paste,
which is error-prone and hampers maintainability.

It's also the case that C is a rather inflexible language. Macros can be
extraordinarily helpful in reducing boilerplate: in unit testing, for example.
I won't argue that macro definitions are the easiest things to read (usually
they're OK but they can get pretty arcane), but I do think something that cuts
a file down from 8,000 lines to 1,000 is at least worth considering.

Macros can also help you maintain type safety. Colin Perciva demonstrated this
a little with his elasticarray, but khash is another example of using macros
to dynamically create type safe data structures. It can also help the compiler
optimize your code.

> Don't use fixed-size buffers

Everything is a fixed-size buffer until you change its size. If you malloc a
buffer of 1024 bytes and read 1025 bytes of user input into it, you overflowed
anyway. The principle ought to be "check your bounds", which applies whether
your buffer is stack/heap.

> Do not use a typedef to hide a pointer or avoid writing “struct”.

I'm with you on pointer hiding (looking at you FreeType), but "struct" is just
far too verbose. You don't accidentally pass things by value because the
compiler will tell you you're passing the wrong type. You won't think it's
actually a scalar because you have a grand total of 3 scalars in C
(bool/int/float), and if you don't know the types you're working with in your
functions you should probably look them up.

You probably don't think this is a big issue because you don't adhere to 80
columns in your code (I looked @ your GitHub briefly), but let me tell you you
run out of space real quick, and "struct" is practically meaningless.

I found a good example:

    
    
      static void set_background(struct wl_client *client, struct wl_resource *resource,
      		struct wl_resource *_output, struct wl_resource *surface) {
    

vs.

    
    
      static void set_background(wl_client *client, wl_resource *resource,
                                                    wl_resource *_output,
                                                    wl_resource *surface) {
    

These are obviously not ints, it's a lot easier to read, and it fits in
terminals. "struct" is honestly just noise.

> GNU is a blight on this Earth

Come on now. I hate GNU indentation as much as any reasonable person, but I
don't think we need to go this far.

\---

Unrelated: aerc looks great! I've been thinking about moving off gmail and
moving more of my life back into the terminal (I used to be all mutt and IRC
and now I'm gmail and hangouts :/ ), and I really like aerc's well-organized
code. Nice work.

~~~
Sir_Cmpwn
>I disagree with these. It's often critical to avoid a function call in hot
paths, and if you don't use macros or inline you have to resort to copy/paste,
which is error-prone and hampers maintainability.

This advice is to be taken with a grain of salt, as with all programming
advice. If you have a hot path, you should do whatever is necessary to meet
the requirements, including macros or inline functions. The caviat, though, is
that performance critical code that demands that is rare.

>It's also the case that C is a rather inflexible language. Macros can be
extraordinarily helpful in reducing boilerplate: in unit testing, for example.
I won't argue that macro definitions are the easiest things to read (usually
they're OK but they can get pretty arcane), but I do think something that cuts
a file down from 8,000 lines to 1,000 is at least worth considering.

I'm dissatisfied with all unit test frameworks for C that I've encountered. I
briefly gave an example of how it might be done better in an unrelated blog
post, would like to hear your thoughts:
[https://drewdevault.com/2016/07/19/Using-Wl-wrap-for-
mocking...](https://drewdevault.com/2016/07/19/Using-Wl-wrap-for-mocking-
in-C.html)

>Macros can also help you maintain type safety. Colin Perciva demonstrated
this a little with his elasticarray, but khash is another example of using
macros to dynamically create type safe data structures. It can also help the
compiler optimize your code.

I mentioned in response to cperciva's post that this is a tricky one. I
acknowledge both sides of this discussion as valid but fall on the "just use
void*" side. Not sure how it helps the optimizer out, though.

>Everything is a fixed-size buffer until you change its size. If you malloc a
buffer of 1024 bytes and read 1025 bytes of user input into it, you overflowed
anyway. The principle ought to be "check your bounds", which applies whether
your buffer is stack/heap.

Well, I didn't say to just use fixed size buffers on the heap. I said to
measure what you need and allocate that much. I probably should have phrased
this more about just checking bounds in general, though.

>I'm with you on pointer hiding (looking at you FreeType), but "struct" is
just far too verbose. You don't accidentally pass things by value because the
compiler will tell you you're passing the wrong type. You won't think it's
actually a scalar because you have a grand total of 3 scalars in C
(bool/int/float), and if you don't know the types you're working with in your
functions you should probably look them up.

Addressed in other comments.

>You probably don't think this is a big issue because you don't adhere to 80
columns in your code (I looked @ your GitHub briefly), but let me tell you you
run out of space real quick, and "struct" is practically meaningless.

I actualy do, but I use 4 wide tabs, and as in all things I permit the
occasional exception to the rule.

    
    
        static void set_background(struct wl_client client,
            struct wl_resource resource, struct wl_resource _output,
            struct wl_resource surface) {
    

I don't mind adding the extra newlines. It's not a big deal. These standards
also evolve over time, and I've become more strict (check out chopsui for more
a recent C example). I'm also lenient on columns from pull requests. I
actually code on a VT220 sometimes, I do value width :)

No comment regarding GNU.

>Unrelated: aerc looks great! I've been thinking about moving off gmail and
moving more of my life back into the terminal (I used to be all mutt and IRC
and now I'm gmail and hangouts :/ ), and I really like aerc's well-organized
code. Nice work.

Glad you like it! It's not ready for prime time, but maybe you'd be interested
in contributing? I rely heavily on contributors to get so many projects done.

~~~
camgunz
I'm not a mocking expert, but of all the solutions I've seen -Wl,--wrap=[blah]
feels like the most straightforward, yeah. I also like how it's in your build
system and you don't write a bunch of twisted code or auto-gen'd headers and
redirects. It's appealing enough to make me wonder if there's an obvious
reason people don't use it.

I'll put aerc on my list of projects to watch. I'm swamped with overly-
ambitious hobby projects but sometimes I need a breath of fresh air :)

------
signa11
this video:
[https://www.youtube.com/watch?v=443UNeGrFoM](https://www.youtube.com/watch?v=443UNeGrFoM),
from a talk by eskil-steenberg titled 'how i program in c' is, imho, pretty
good as well.

------
golergka
Great post, but most of it is relevant for all other languages as well.

------
oleks
These principles, at least by name, apply in general, not just to C.

------
infradig
Meh, skip on this one.

------
jankedeen
1\. The only reason to use C these days _is_ performance and portability.
Write the code to your level of expertise or use a different language to cater
to the 'du jour' audience.

2\. This was advice straight of Der lindens Deep C secrets back in the mid
90s. It wasn't always true then and it isn't always true now. I often use
macros and don't find them a problem to understand in others code.

3\. Give me a break. Except when you don't need to allocate and manage memory
which is almost every time you write a tool that does one thing well. This is
not a good general principle.

4\. This is the way it works for me. Do it. Nope.

5\. Ok, some of this is common sense writing portable code. 'GNU is a blight
on this earth': "..now we see the violence inherent in the system.."

6\. Yes, finally a good point but not about C per-se. 7\. Ok, but this is not
about C per-se. 8\. Hrrmmm. A culture of blame is what produces restrictive
rules like these.

This article is fodder for the Rust and other anti-c devotees here to glom
onto and point out all the problems with the author and his language as the
ultimate straw man. To me it sounds like this person wasn't writing C in the
90s.

------
randcraw
In case drewdevault.com has been blacklisted by your web proxy (as it is for
mine), here's an alternate source:

[https://sircmpwn.github.io/2017/03/15/How-I-learned-to-
stop-...](https://sircmpwn.github.io/2017/03/15/How-I-learned-to-stop-
worrying-and-love-C.html)

