
How to C in 2016 - fcambus
https://matt.sh/howto-c
======
mapleoin
_Writing correct code at scale is essentially impossible. We have multiple
operating systems, runtimes, libraries, and hardware platforms to worry about
without even considering things like random bit flips in RAM or our block
devices lying to us with unknown probability._

 _The best we can do is write simple, understandable code with as few
indirections and as little undocumented magic as possible._

Applies very well to all programming.

~~~
hal9000xp
I'm highly disagree that it's impossible to write high quality code in C
language for a big project (I don't like his statement: The first rule of C is
don't write C if you can avoid it).

I was mostly C developer (80% C, 20% C++) for 4 years in a big project (I was
backend developer of ICQ Instant Messenger). It has more than 2M lines of C
code. Almost all libraries was written from scratch in C language (since ICQ
is very old project, many of essential stuff was written within AOL).

I felt comfortable to write high level code in C.

When I read Nginx source code (it's written in C), I see better code quality
than 90% of proprietary commercial software which is written on nice and
comfortable high level languages.

Good developer is able to write high quality code in C language with minimum
bugs in large scale project. Regardless of how nice and high level and
convenient language, bad developer will write c....y code.

You can also check my personal project which is written entirely in C. (I
didn't contribute for 1.5 years to my github since I was changed countries of
living, jobs etc. I hope I will return to it when my life become stable
again).

P.S. To be clear, I think that C++11 is great language. But unlike many C++
developers, I love pure C.

~~~
defen
This is why we can't have these discussions...everything gets interpreted
literally, and it becomes some sort of ego contest. I'll take your claim at
face value, that your team wrote 2M lines of C for a commercial project, with
no memory corruptions, crashes, integer overflows, etc. That's a shockingly
impressive achievement, one that I think has probably never been done before.

But how is that a useful or replicable result, that will work in the real
world for other projects? You're basically saying that everyone just needs to
be a top 0.001% C programmer. This is the "abstinence-only sex education" of
programming advice. It's super simple to not get STDs or have out-of-wedlock
pregnancies - just don't have sex with anyone but your spouse, and ensure they
do the same. It's 100% guaranteed to work, and if you don't follow that advice
you're stupid and deserve whatever bad things happen to you.

~~~
caf
"out-of-wedlock pregnancies"?

What is this, 1953?

There are a great number of couples choosing to have children in stable,
committed - yet unmarried - relationships.

~~~
defen
This sums up internet commenting in 2016. Congratulations on _entirely_
missing the point because you chose to focus on an irrelevant and pointless
detail, which I'm sure will spawn a stupid subthread that changes no one's
mind.

~~~
spajus
HN needs "collapse whole thread" feature. Badly.

~~~
MagerValp
An official solution would be great, but in the meantime there was a thread
last year with lots of options:

[https://news.ycombinator.com/item?id=8706496](https://news.ycombinator.com/item?id=8706496)

------
Sir_Cmpwn
>The first rule of C is don't write C if you can avoid it.

Man, this is just lame. C is a really nice, useful language, especially if you
learn the internals. It requires discipline and care to use effectively, but
it can be done right. The problem is that a lot of C programmers only know C
and can't apply higher level concepts or abstractions to it because they've
never learned anything else. This makes their code harder to maintain and
easier to break.

~~~
lostcolony
He's not saying that C isn't useful, but that for most problems it's overkill.
Why inflict the extra burden on yourself -for no benefit-? Most code being
written can be done just as well in a language with garbage collection, that
has more powerful abstractions and paradigms as first class citizens...why not
take advantage of them? Of course you can write those higher level
concepts/abstractions in C, but the point is, -they've already been written-,
tested, and documented, by other people. Why write untested, undocumented code
that reimplements those things when you have no compelling reason to?

~~~
kqr
There are even languages without garbage collection that makes a lot of things
easier than they are in C. See Ada, Rust, ~~Go~~ D and others.

~~~
rhodysurf
Rust is great, but I think C is much easier

~~~
catnaroek
You mean it's easier to write an incorrect program in C than a correct one in
Rust? Of course!

If you mean it's easier to write correct programs in C than correct programs
in Rust, I'm afraid you're badly mistaken.

~~~
whyever
While I agree with your sentiment, I think this is not true in general. The
Rust compiler sometimes rejects correct programs, in which case it can be
harder to write it in Rust than in C.

~~~
pcwalton
> The Rust compiler sometimes rejects correct programs, in which case it can
> be harder to write it in Rust than in C.

Well, the C compiler sometimes rejects correct programs too.

~~~
crzwdjk
Sometimes C compilers can't even agree on what counts as "correct"! With Rust,
you can at least be reasonably sure that if your program compiles with your
version of the compiler, it will continue to do so with all later versions,
because they're coming from the same codebase and made by people who care
about backward compatibility.

~~~
catnaroek
> Sometimes C compilers can't even agree on what counts as "correct"!

This comes primarily from the fact the C standard itself is hard to interpret
right. Standard ML has less issues in this department: since the Definition is
clearer and less ambiguous, implementations disagree less on what counts as
correct code and what its meaning is. So having only one major language
implementation isn't the only way to prevent compatibility issues.

~~~
kqr
Hard to interpret right... according to some people, there is actually _no_
valid reading of it.[1]

[1]: [http://www.open-
std.org/jtc1/sc22/wg14/www/docs/n1637.pdf](http://www.open-
std.org/jtc1/sc22/wg14/www/docs/n1637.pdf)

------
_wmd
Some of this seems like horrendously bad advice:

\- Using fixed width integers in for loops seems like a fabulous way to reduce
the portability of code

\- the statements in "C allows static initialization of stack-allocated
arrays" are _not_ equivalent, one is a bitwise zeroing while the other is
arithmetic initialization. On some machines changing these statements blindly
will cause a different bit pattern to end up in memory (because there are no
requirements on the machine's representations for e.g. integers or NULL or
..). There are sound reasons why the bitwise approach could be preferred, for
example, because a project has debug wrappers for memset that clearly
demarcate uninitialized data

\- the statements in "C99 allows variable length array initializsers" aren't
even slightly equivalent. His suggestion uses automatic storage (and
subsequently a stack overflow triggered by user input -- aka. a security bug)

\- "There is no performance penalty for getting zero'd memory" this is
bullshit. calloc() might optimize for the case where it is allocating from a
page the OS has just supplied but I doubt any implementation ever bothered to
do this, since it relies on the host OS to always zero new pages

\- "If a function accepts arbitrary input data and a length to process, don't
restrict the type of the parameter." the former version is in every way more
self-documenting and consistent with the apparent function of the procedure
than his use of void _. It also runs counter to a rule from slightly more
conservative times: avoid void_ at all costs, since it automatically silences
all casting warnings.

Modern C provides a bunch of new things that make typing safer but none of
those techniques are mentioned here. For example word-sized structs combined
with struct literals can eliminate whole classes of historical bugs.

On the fixed width integers thing, the size of 'int', 'long' and 'long long'
are designed to vary according to the machine in use. On some fancy Intel box
perhaps there is no cost to using a 64bit type all the time, but on a
microcontroller you've just caused the compiler to inject a software
arithmetic implementation into your binary (and your code is running 100x
slower too). He doesn't even mention types like intfast_t designed for this
case, despite explicitly indicating "don't write C if you have to", which in
2016 pretty commonly means you're targeting such a device

~~~
Nursie
>> \- Using fixed width integers in for loops seems like a fabulous way to
reduce the portability of code

Can you explain this? I'm not seeing it.

You should generally know the range of numbers you're seeking to store in an
integer variable, if you don't then you're asking for trouble.

~~~
IshKebab
Yeah in fact using _non_ -fixed width integers reduces portability!

I ran into a bug where I was running some Arduino code (16-bit) on an mBed
(32-bit). The use of `int` rather than `uint16_t` caused a timeout to become
an infinite loop.

I'm pretty sure it would be impossible to have portability issues due to using
fixed-width types - I mean their entire point is to ensure portability.

~~~
annathebannana
Do you have any reason for supporting uint16_t instead of uint_least16_t?

Their point is against portability as they may not exist at all.

~~~
masklinn
> Their point is against portability as they may not exist at all.

The good part being that you're warned at compile time, not when your 2^16
elements loop becomes a 2^32 elements loop.

~~~
annathebannana
Both problems are being avoided if you use [u]int_leastN_t

Or if you do not make assumptions of the sizes of int, something that only bad
programmers do.

~~~
masklinn
> Both problems are being avoided if you use [u]int_leastN_t

No you'll have the exact problem I cited in that case.

> Or if you do not make assumptions of the sizes of int, something that only
> bad programmers do.

Requesting a specific data size is the exact opposite of making assumptions.

~~~
boomlinde
Well, the assumption you have typically been making, when your unsigned loop
counter is causing problems because it is larger than expected, is that the
counter will exhibit a particular behavior when it over- or underflows. IIRC,
this is unspecified in the standard and you can't write portable code making
such assumptions.

I can see how the type changing to a smaller one might cause problems, but I
don't see how IshKebab's example could happen without exploiting
implementation specific overflow behavior.

~~~
masklinn
> I can see how the type changing to a smaller one might cause problems, but I
> don't see how IshKebab's example could happen without exploiting
> implementation specific overflow behavior.

INT_MAX (or INT_LEASTN_MAX for annathebannana's suggestion) doesn't require
exploiting overflow behaviour, but going from 2^15 iterations to 2^31 or 2^61
iterations may be problematic.

~~~
boomlinde
Problematic, yes, but not the difference between a terminating loop and an
endless loop, which is what he said was the result of the size change. If that
actually is the case I'd guess the problem was that the compiler for the
latter platform was removing a zero comparison loop condition based on the
assumption that a value that only ever increments can only ever be zero once,
using the unspecified nature of ocerflows to its advantage, while the former
did not.

And using INT_MAX or similar for what sounds like a timing loop is a whole
other can of bad practice. Then the problem isn't that you used the wrong
type, it's that you used the wrong value.

~~~
masklinn
> Problematic, yes, but not the difference between a terminating loop and an
> endless loop

If the loop does significant work and was calibrated for an expectation of 65k
iterations, stepping to 2 billion (let alone a few quintillion) is for all
intents and purpose endless.

> And using INT_MAX or similar for what sounds like a timing loop is a whole
> other can of bad practice. Then the problem isn't that you used the wrong
> type, it's that you used the wrong value.

No objection there, doing that is making invalid assumptions, my point is that
moving to exact-size integral does _fix_ it.

~~~
boomlinde
> If the loop does significant work and was calibrated for an expectation of
> 65k iterations, stepping to 2 billion (let alone a few quintillion) is for
> all intents and purpose endless.

And if it's not, it's not, the point being that endless in this case would be
meaningless outside it's literal meaning unless we know more about the
specific case.

> No objection there, doing that is making invalid assumptions, my point is
> that moving to exact-size integral does _fix_ it.

No, using an exact _value_ fixes it. Any unsigned integer type is just fine
for any integer value from 0 to 65535. If you change the type to a larger
integer type without changing the supposed iteration count, the code would not
have this problem, and if you changed the value to something higher than 65535
without adjusting the size of the type, you would have a different problem.
Thus, the problem described here does not pertain to the type of the variable
used.

------
AndyKelley
The author linked to this site:
[https://www.securecoding.cert.org/confluence/display/c/SEI+C...](https://www.securecoding.cert.org/confluence/display/c/SEI+CERT+C+Coding+Standard)

These are wonderful. Concise nuggets of knowledge that you can keep in your
head, little warnings of what not to do.

Side note, it's really fun reading this article about modern C given that I'm
working on an experimental programming language meant to replace C [1]. Just
today I got the Guess Number Game example working, without a dependency on
libc [2] (caveat: Linux x86_64 only so far). So, I'm having fun trying to
think about how C could be better, reading implementations of various libc
functions, and writing my own "standard library".

For example, in my language, there are no `int`, `unsigned char`, `short`
types, there are only `i32`, `u8`, `i16`, etc (I shamelessly stole this idea
from Rust). This mirrors the article's suggestion to only use int32_t,
uint8_t, int16_t, etc. But, since it's a new programming language, we don't
have the other types sitting there as a red herring.

[1]: [https://github.com/andrewrk/zig](https://github.com/andrewrk/zig)

[2]:
[https://gist.github.com/andrewrk/131e6f48a15c42fc5ca8](https://gist.github.com/andrewrk/131e6f48a15c42fc5ca8)

~~~
ilek
>only `i32`, `u8`, `i16`

While these are not new ideas - Why not use s32 rather than i32 and so on.
Makes more sense if you use something like u32 too.

I think it's important when making another language to really question
everything you do in it. Why use this operator, or why use this mnemonic for
built in types?

~~~
masklinn
> While these are not new ideas - Why not use s32 rather than i32 and so on.
> Makes more sense if you use something like u32 too.

i denotes an integral, as opposed to the also-signed float types?

------
tines
Most of this advice is pretty standard, but this stood out:

> You should always use calloc. There is no performance penalty for getting
> zero'd memory.

But isn't there? This may be true the first time the program requests a new
page from the OS, but when your program starts reusing memory there's going to
be a performance hit.

~~~
nappy-doo
Yes, but it's more subtle. The argument that there's no performance different
between malloc/calloc is only valid if you have an MMU that will zero pages.
Lots of embedded systems don't have such MMUs (or even an MMU at all);
therefore, there is a difference between malloc and calloc.

~~~
bnegreve
But desktop processors also don't zero pages, do they?

And by zeroing pages, you prevent lazy memory allocation policies which do not
allocate the pages until they're read or written for the first time. This can
have a significant impact on memory usage, data locality etc.

So I'm quite skeptical that there is no difference between calloc and malloc.
Is there more evidences of this somewhere?

~~~
kqr
How does it prevent such allocation? Can you not just mark the first read from
an area to always return 0, and then allocate as soon as it happens? That also
has the benefit of not having to zero anything if you're only writing to it.

~~~
DSMan195276
Internally, an OS like Linux maps _every_ page in a requested piece of memory
to a read-only page of zeros. When you attempt to read from this page, it
works fine. When you attempt to write to it, the MMU causes a page-fault and
the OS inserts a page where the zero-page is, and then continues your program
execution. Thus, it doesn't actually have to allocate any memory for you if
you never _write_ to the new pages.

But the OS/MMU doesn't distinguish between a regular write, and a write of
zero. Thus, if you manually zero every page you get (And thus write zeros to
the read-only zero-page), it'll page-fault back to the OS and the OS will have
to allocate a new page so that the write succeeds - Even though if you didn't
do the zeroing of memory you would have gotten the same effect of having a
bunch of zeros in memory, but without having to allocate any new pages for
your process.

~~~
kqr
Isn't that just saying that calloc is compatible with lazy allocation?

~~~
DSMan195276
Kinda. Reading my comment a second time, I'm not exactly happy with my
description, since while it's 'right' is a very simplistic description,
ignoring some of the finer points.

Since malloc/calloc are generally used for smaller allocations, the chances
you can actually avoid allocating some pages you ask for is pretty slim since
a bunch of objects get placed into the same page (And thus writing to _any_ of
them will trigger a new page being allocated). There's also no guarantee there
isn't headers for malloc to make use of, or similar surrounding your piece of
memory, which makes the point moot - Just using malloc triggers writes to at
least the first page. So while calloc/malloc are kinda compatible with lazy-
allocation, you really shouldn't rely on it being a thing, and it probably
won't matter.

It's worth understanding, but the chances it _actually_ comes into play aren't
huge. If your program does lots of small malloc's and free's, then it
basically won't matter because you won't be asking the kernel for more memory,
just reusing what you already have.

If you care about taking advantage of lazy-allocation for one reason or
another, the bottom line is probably that you shouldn't be using malloc and
calloc for that then. Just use mmap directly and you'll have a better time -
more control, you have a nice page-aligned address to start with, and you can
be sure the memory is untouched. malloc and calloc are good for general
allocations, but using mmap takes out the guesswork when you have something
big and specific you need to allocate.

------
annathebannana
Terrible

The first rule of C is to ALWAYS use C

The second rule of C is to NEVER use [u]intN_t, use [u]int_leastN_t instead
and (unsigned) char when you want to deal with bytes

The 3rd rule of C is to never use anything else when size_t should be used

The 4th rule of C is that everyone has his own style, so declaring variables
on top is totally acceptable (and more clean IMO)

The 5th rule of C is "enjoy your stack overflow" when using VLAs instead of
malloc (not to mention that VLAs are optional in C11)

>which also means it's capable of holding the largest memory offset in your
program.

wrong

>C99 gives us the power of <stdbool.h> which defines true to 1 and false to 0

I do use it but it is useless

>readability-braces-around-statements — force all if/while/for statement
bodies to be enclosed in braces

don't use a shitty editor

>You should always use calloc

good thing calloc has: checks for overflows when multiplying the typesize and
the arraylen

bad thing: makes programmers think that it will make your pointers null

>you can wrap it with #define mycalloc(N) calloc(1, N).

but the two arguments is the only good thing calloc has

>growthOptional

I can't see any reason why the second one is considered better

~~~
jimjag
++1

------
vitaut
It's a bit sad that the formatting API hasn't improved in C over the years.
Take this code, for example:

    
    
      printf("Local number: %" PRIdPTR "\n\n", someIntPtr);
    

which is quite unreadable to say the least. To address this issue I even wrote
a small library
([https://github.com/cppformat/cppformat](https://github.com/cppformat/cppformat))
that allows you to write something like this instead:

    
    
      print("Local number: {}\n\n", someIntPtr);
    

It is written in C++ rather than C, because the latter doesn't have facilities
(at least overloading is necessary) to implement such library.

~~~
jhallenworld
This is quite a nice library, I'll try it. Enhancement requests:

\- Option to insert commas or underscores every three digits (for decimal) or
four digits (hex/binary/octal).

\- Option to print floating in engineering format (make exponent a multiple of
3).

\- Option to print floating but with a specific given exponent.

~~~
vitaut
Thanks for the suggestions. There has been some work to make formatting more
customizable
([https://github.com/cppformat/cppformat/issues/235](https://github.com/cppformat/cppformat/issues/235)).
This will enable extensions like the ones you suggest while keeping the
library itself lean. Generally useful extensions like thousand separators can
be added to the core library of course. They are actually on my TODO list.

------
zrm
The article says not to do this:

    
    
      uint32_t numbers[64];
      memset(numbers, 0, sizeof(numbers) * 64);
    

That is certainly true because that is a buffer overrun.

~~~
jacquesgt
Which is a perfect example of why their approach is better. Less arithmetic to
get right with their approach.

~~~
minitech
To be fair, the correct version isn’t very arithmeticky.

    
    
        memset(numbers, 0, sizeof numbers);

------
btrask
I recently wrote a similar article ("C Programming Substance Guide")[1] which
made the rounds here[2]. I've updated and improved it quite a bit since then
too. It seems like we agree on the major points but it might be a good second
opinion.

[1]
[https://github.com/btrask/stronglink/blob/master/SUBSTANCE.m...](https://github.com/btrask/stronglink/blob/master/SUBSTANCE.md)

[2]
[https://news.ycombinator.com/item?id=10157018](https://news.ycombinator.com/item?id=10157018)

------
to3m
One point about char is that a pointer to a character type (character types
are char, unsigned char, signed char - see 6.2.5.15) may alias non-char
objects in a standard-compliant fashion (see 6.3.2.3.7). But uint8_t and
int8_t may not necessarily be suitable replacements, because they are defined
to be integer types (see B.19 - character types are a subset of integer
types), and in any event may not even exist (7.20.1.1.3).

~~~
joveian
I think it isn't clear what the standard means here. If uint8_t exists it must
be a typedef to unsigned char (edit: unless to3m is right that there could be
a distinct 8-bit implementation-defined integer type, which I didn't think was
allowed but maybe it is), so the question is if a typedef to a character type
is a character type. My reading is that it isn't, but I've heard enough good C
programmers assume that it is that either I am wrong or hopefully compilers
will always allow uint8_t to work in this case. It certainly makes more sense
to use uint8_t than unsigned char for that usage, but ideally maybe there
would be a standard byte_alias_t or such.

~~~
to3m
A typedef to a character type is a character type. See
[http://port70.net/~nsz/c/c11/n1570.html#6.7.8p3](http://port70.net/~nsz/c/c11/n1570.html#6.7.8p3).

[http://port70.net/~nsz/c/c11/n1570.html#6.2.5p4](http://port70.net/~nsz/c/c11/n1570.html#6.2.5p4)
leaves open the option for extended signed integer types, and the following
paragraph provides for an unsigned integer type for each signed type, extended
signed types included.

So it would be perfectly possible, if not actually very reasonable - not that
this is stopping anybody at the moment - for an implementation to provide
__int8, a signed 8-bit integer type that fulfils all the requirements of
uint8_t, but is not a character type, and use that as uint8_t. Then,
conceivably, it could fail to support the use of uint8_t pointers to alias
other objects, something supported by the more usual situation of using
unsigned char for uint8_t.

I'm not sure that anybody would do this, but they could. I'm rather surprised
gcc doesn't do it, come to think of it, just to teach its users a lesson.
Maybe I should file a bug report?

~~~
joveian
Thanks, good to know the full situation. I'll try to remember not to use
uint8_t that way in the future, just in case.

I looked briefly previously and somehow got the sense that extended types
could only be larger than standard types, but this time I found that 6.3.1.1
explicitly mentions same width extended types.

Personally, I've been happy with GCC's decisions on such issues.

------
golergka
As someone who has a been trying to get into modern C from higher level
languages and looking for more modern sources than K&R, THANK YOU. (Even if
you just posted the link here and have no relation to original author). (Of
course, this post is not the only place to learn about modern C, but still, a
very useful one).

~~~
joshvm
There's 21st Century C by O'Reilly, which I enjoyed. I'm not qualified to say
whether you should read it, but there seems to be a lot less backlash towards
it than, say, Learn C The Hard Way. My main criticism is that the author
wastes half the book on good development practice which is all well and good,
but not if you're just there for the C. For developers not familiar with make,
testing and so on, it's great.

As for the actual C content, frankly this was where I went to when I was
wondering how to write better (modern) C, so I don't really know any better.
The writing is almost certainly opinionated and as above, could be a lot more
comprehensive.

(oops edit window left open for a while, see sibling!)

------
tsavola
> LTO fixes the "source analysis and optimization across compilation units
> problem" by annotating object files with intermediate representation so
> source-aware optimizations can be carried out across compilation units at
> link time (this slows down the linking process noticeably, but make -j
> helps).

make -j doesn't help linking speed, unless multiple binaries are begin built.

~~~
delinka
It's not the linking process that make -j speeds up, but the optimization
part.

~~~
tsavola
AFAIK the bulk of link time optimization happens in the linker. The
parallelizable compiler invocations just store extra information in the object
files.

The linker invocation is a single make job, which is the unit of make -j
parallelism.

------
deevus
I wrote an assignment in C for Operating Systems in my second year of CS. I
did it mostly for the challenge and ended up with a distinction grade.

It was very challenging! I was learning how to do threading in C for what was
one of the most complex assignments in my degree. However, I don't regret a
moment of it. Even with a mark that was lower than my average programming
assignment marks, I learned more in that assignment than I did in anything
else while at uni.

Being forced to RTFM because it wasn't abstracted away for you was
exhilarating once I understood what was going on. And man, it was fast! Much
faster than others who had programmed in Java.

I wish I had more reasons to write C, because I love it.

------
jhallenworld
>If you find yourself typing char [..] into new code, you're doing it wrong.

>The only acceptable use of char in 2016 is if a pre-existing API requires
char (e.g. strncat, printf'ing "%s", ...) or if you're initializing a read-
only string (e.g. const char _hello = "hello";) because the C type of string
literals ("hello") is char _.

Heh, basically you need to use 'char _' for strings. I would far prefer if
strings were unsigned so that I could use 'uint8_t _', but if you try it
you'll get tons of conversion warnings and your code will look weird.

~~~
bluetomcat
The other widespread use of char pointers is when you need to perform pointer
arithmetic over a buffer (without any dereferencing) in steps of 1 (as
sizeof(char) == 1).

Void pointers do not allow pointer arithmetic (GCC allows it as an extension).

~~~
gvb
He covers that in the next section "Signedness" and more thoroughly in
"System-Dependent Types" where he recommands (properly) using uintptr_t and
intptr_t:

 _The correct type for pointer math is uintptr_t defined in <stddef.h>._

~~~
bluetomcat
Correct me if I'm wrong, but in my understanding, the primary purpose of
uintptr_t is to type-pun a pointer into a compatible integer, inspect/modify
the bits of the integer and then cast it back to a pointer of the same type. I
don't feel like it's the right type for simple pointer arithmetic.

~~~
_kst_
You're right, using uintptr_t for pointer arithmetic is a bad idea.

I've worked on systems (Cray vector machines) where converting a pointer to an
integer type, performing arithmetic on the integer, and converting back to a
pointer could yield meaningless results. (Hardware addresses were 64-bit
addresses of 64-bit words; byte pointers were implemented by storing a 3-bit
offset in the high-order bits of the pointer.)

Pointer arithmetic works. It causes undefined behavior if it goes outside the
address range of the enclosing object, but in those cases converting to
uintptr_t and performing arithmetic will give you garbage results anyway.

------
bluetomcat
> Use: > ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew;

The casts to uintptr_t are not needed in this case, as the result of
subtracting two pointers already yields ptrdiff_t.

Also, the mere subtraction of two pointers that do not point to the same
"array object" yields undefined behaviour as per the standard.

~~~
stromgo
The casts are needed for "dirty pointer math". Without the casts, &A[1] -
&A[0] returns 1, not 4, for an int *A.

~~~
sltkr
The code is still wrong for another reason: if (uintptr_t)ptrOld <
(uintptr_t)ptrNew, then the subtraction is calculated modulo some power of 2
yielding a large number. The result of converting this to a signed integral
type like ptrdiff_t is implementation defined.

In practice it works only if sizeof(ptrdiff_t) == sizeof(uintptr_t) AND the
system uses two's complement for signed integers. Neither property is
guaranteed by the standard.

I think the correct way to find the byte offset is this:

    
    
      ptrdiff_t diff = (char*)ptrOld - (char*)ptrNew;

~~~
_kst_
And it's wrong for yet another reason. You can convert a pointer to uintprt_t,
but arithmetic on the result isn't necessarily meaningful.

~~~
shawn-furyan
This thread makes me eager to get back into the minimal C programming that I
left behind in my college assembly class.

~1 beat~

Yikes!

~leaves self-shaped dust cloud behind as hurriedly runs toward higher [level]
ground~

------
mrich
Quite concise and short. Now wouldn't it be great to also have such a short
guide for C++... (thinking about the tome that is
[https://github.com/isocpp/CppCoreGuidelines](https://github.com/isocpp/CppCoreGuidelines))

~~~
nwatson
My head's been mostly in Java and Python the last three years, and my old
work's C++ practices were woefully out-of-date. I really appreciate the work
put into the CppCoreGuideline documents ... it's real complex, though.

I was dreading getting back to C++ with the Rule-of-3 and now the Rule-of-5 --
what a pain! Fortunately I ran into the Rule-of-Zero as described here:
[http://accu.org/index.php/journals/1896](http://accu.org/index.php/journals/1896).
The CppCoreGuidelines have a more restrictive Rule-of-Zero but allude to the
link's form . . . the only way I'll get back to C++ is if I can avoid move-
constructor and move-assignment for the majority of my work ... and the link
provides the answer!

------
Nursie
Usually I find something very objectionable in these C guides, but I like this
one, and it's hit some of my favourite/most hated bugbears. In recent years
it's nearly driven me mad seeing people use the old types either directly or
re-implementing their own equivalent of stdint.h, complete with all sorts of
platform switching and other nonsense.

We've got these facilities, they're not exactly bleeding edge (the clue's in
the name C99), let's use them!

~~~
stromgo
The document recommends "intptr_t" over "long" for system-dependent types, and
I've considered doing this but I've been put off by the printing type of
%"PRIdPTR" instead of %ld. I wonder if on modern platforms it'd be ok to use
intptr_t and the printing type %zd (meant for ssize_t).

~~~
Nursie
Yeah I must admit the printing types are a bit ugly!

------
haldean
One trick I've been using a lot recently that saves me from forgetting to
check error codes is the judicious use of
__attribute__((__warn_unused_result__)) on functions that return an error
code. It prevents you from being able to ignore the return value without doing
something with it; even just assigning it to a variable and never checking the
value in the variable isn't enough to silence the warning.

------
agentultra
Nice list!

I like using anonymous structs with variable initializers to provide
parameters to functions with default values -- nice and clean!

C99 is not your grandparents' C. It has kept up with the times.

A more thorough resource I cannot recommend highly enough: 21st Century C[0]

[0]
[http://shop.oreilly.com/product/0636920033677.do](http://shop.oreilly.com/product/0636920033677.do)

------
jedisct1
And then, someone points out that your code doesn't work in Visual Studio, and
that, of course, you have to support VS < 2015.

So, you're back to writing C89, in 2016.

~~~
floatboth
#ifdef _MSC_VER #error "Do not use Microsoft compilers." #endif

------
vbezhenar
How to C in 2016: use valgrind from the start. I think that it's the best tool
for C in the world.

~~~
amadvance
And don't forget about helgrind and drd for multithread code.

I would not even dare to use threads without the support of these tools. In
fact, this seems a side where C has a big advantage over other languages.

------
hyc_symas
The biggest failure of this article is the multiple admonishments "never do
<blah> (except for this, this, and that other case over there" \- all advice
of this form is worthless when your supposed thesis is making code readable
and consistent.

Consistency means avoiding special cases. Variable length arrays are too prone
to special cases - IMO, just don't use them. malloc vs calloc is too prone to
special cases, that rule is worthless. The rule "never use memset" is full of
special cases, also worthless. (And in fact, for your larger automatic
initializations, gcc just generates code to call memset anyway.) I would say
"always use memset when you need zeroing - then you'll never be surprised."
The fewer special cases you have to keep track of, the better.

"Never cast arguments to printf - oh but for %p, always cast your pointers to
(void *)" again, worthless advice with an unacknowledged special case.

The way to write code that always works right is to always use consistent
methodology and avoid any mechanism that is rife with special cases.

The advice "always return 'true' or 'false' instead of numeric result codes"
is worthless. Functions should always return explicit numeric result codes
with explicitly spelled out meanings. enum is nice for this purpose because
the code name is preserved in debug builds, saves you time when working inside
a debugger. But most of all, it's a waste of time for a function to return a
generic false/fail value which then requires you to take some other action to
discover the specific failure reason. POSIX errno and Windows GetLastError()
are both examples of stupid API design. (Of course, even this rule has an
exception - if the function cannot fail, then just make it return void.)

------
tomcam
For me, this is the most important bookmark of the year. Dropping back into C
after using pre C11 it is tailor made for me. Covers all the areas I needed:
Correct non-K&R datatypes, new formatting options, best practices on arrays.
Awesome.

------
jzwinck
The clang-format wrapper script presented has a bug. The $@ at the end must be
quoted for it to work on filenames containing spaces.

For bonus points it shouldd prefix the clang-format command with "exec" but
that's not a correctness issue.

~~~
minitech
Do you mean “must not be quoted”? $@ shouldn’t be, most of the time.

~~~
jzwinck
No typo. It must be quoted as "$@". If you think otherwise, test with some
filenames containing spaces. Or read here:
[http://stackoverflow.com/questions/448407/bash-script-to-
rec...](http://stackoverflow.com/questions/448407/bash-script-to-receive-and-
repass-quoted-parameters)

------
camgunz
I refuse to believe that the wicked Buffer Overflow has completely stymied
humanity. Use array/string/buffer libraries. They're very small and very easy
to test. Pass them around instead of a "char *" and a "size_t".

Now, if you want to talk about signed overflow, bitwise operations on signed
types, wacky undefined behavior, or memory management, that stuff is pretty
hard to deal with in C. But 99% of the time when people are shaking their
fists at "shitty ol' C ruining the Internet for everyone", they mean buffer
overflows, and it just shouldn't be an issue these days.

------
mrcrassic
> C99 allows variable declarations anywhere

I actually like declaring my variables at the top. To me, this seems more
readable than searching for declaration statements throughout the code.

Why should I not do this?

~~~
humanrebar
When you do that, reading your code is like reading Dune for the first time. I
have to keep jumping back and forth to the reference to figure out what's
going on.

~~~
amq
At least I can clearly see which variables are already set, so that I don't
redeclare them.

------
cpeterso
This article focuses on clang and gcc, but note that MSVC's printf functions
do not support %zu (or %zd) for size_t. MSVC uses %Iu. There is no standard
"PRIuSIZE" macro for size_t, but Mozilla defines a polyfill for Firefox:

[https://hg.mozilla.org/mozilla-
central/file/tip/mfbt/SizePri...](https://hg.mozilla.org/mozilla-
central/file/tip/mfbt/SizePrintfMacros.h)

------
xedarius
I don't think the claim of zero performance penalty for calloc is true. If for
example you're calling calloc on a game console it is 100% not true.

------
roberthahn
In light of the fact that best practices have evolved considerably since K&R,
what book(s) would you recommend for people wanting to learn C today?

~~~
ThatGeoGuy
Modern C by Jens Gustedt: [http://icube-
icps.unistra.fr/img_auth.php/d/db/ModernC.pdf](http://icube-
icps.unistra.fr/img_auth.php/d/db/ModernC.pdf)

It's not exactly 100% complete in the literal sense, but everything covering
the language spec and usage is there. I highly suggest working through it, as
the book itself is incredible for going from beginner to expert in the short
span it does.

~~~
roberthahn
Thank you very much for the link and suggestion! Much appreciated!

------
slmyers
Fantastic article. This is exactly how the language should be taught in
university. At least I wish I was taught to code like this in school.

~~~
saurabhtandon
I learned and used C at school. It appreciate what I learned but using it in
the right way, using the latest functionality available, considering machine
portability along with safe coding guidelines of code would have been better.

------
tptacek
Is there any moral difference between using VLAs and calling alloca? Because
alloca is generally recognized as evil.

~~~
pcwalton
alloca isn't so bad if you have stack guards, which Microsoft has used
extensively for a long time [1]. Historically Unix has lagged behind here,
however, and so alloca is a security risk on Unix, which leads to the "alloca
is evil" advice.

There is also the fact that you permanently lose one register in your function
(ebp) if you use alloca(). This is important on 32-bit x86 but is rarely
discussed.

There is essentially no implementation difference between VLAs and alloca().

[1]: [https://msdn.microsoft.com/en-
us/library/wb1s57t5.aspx](https://msdn.microsoft.com/en-
us/library/wb1s57t5.aspx)

~~~
tptacek
No, I think alloca was considered evil before Elias wrote "Smashing The
Stack". I bet if you go track down the 'alloca' man page from FreeBSD 2.0,
you'll find that it's use was "discouraged" even back then.

~~~
pcwalton
Yeah, you're right:
[https://www.freebsd.org/cgi/man.cgi?query=alloca&apropos=0&s...](https://www.freebsd.org/cgi/man.cgi?query=alloca&apropos=0&sektion=0&manpath=4.3BSD+NET%2F2&arch=default&format=html)

The "machine dependent" reason is out of date though.

------
tilt_error
Having worked with a large code base, maintained over a period of time (25+)
by multiple persons has learned me respect and that even old code have a value
in virtue of being running without problems over time. A piece of software
that is running without problems has a value in itself that have to be taken
into account when considering the cost of maintenance.

Respect earlier developers and try to minimise _unnecessary_ changes that
break version control history. Did earlier developers use names you don't
like? Formatting you don't like? InconsistentCamelCasing or
other_naming_paradigms you don't like? Let it be -- at least until you do a
refactoring where _you_ claim responsibility.

It may not be a good point to uncritically and retroactively apply the good
points from the original article in an old code base.

------
Xophmeister
I thought `ssize_t` was only signed `size_t` in practice. Technically, it only
needs to be `size_t` with -1.

~~~
whyever
How would that work? You need an additional bit.

~~~
julian-klode
You could just store using offsets instead. You don't have to do sign bit.

2 bit example:

00 => -1 01 => 0 10 => 1 11 => 2

or: 2 bit example:

00 => -2 01 => -1 10 => 0 11 => 1

This even wrapsaround naturally.

------
Joky
C (or C++) in 2016 and no mention of ASAN/UBSAN(/TSAN)? This seems like a must
nowadays.

------
synack
Would love to see a discussion of build systems for modern code. Trying to
decide between autoconf/cmake/scons/ninja/gyp for new projects is like
flipping a coin... They all have their advantages and disadvantages and there
is no clear winner.

------
rwmj
After too many problems with "unfriendly C", I finally gave in and now compile
everything with -fno-strict-overflow (the Linux kernel does the same thing). I
don't care about architectures that don't use twos-complement.

------
justncase80
For an example of a high quality modern C api I highly recommend taking a look
at libuv:

[http://libuv.org/](http://libuv.org/)

------
mmphosis
# cc test.c -o test -O3 -Os -Wall -pedantic -Werror -Wshadow -Wstrict-aliasing
-Wstrict-overflow -std=c99

test.c:1:2: error: #import is a GCC extension

~~~
grahamedgecombe
use -std=gnu99 instead

------
csvan
"The first rule of C is don't write C if you can avoid it."

This ought to carved into the walls of R&D departments everywhere.

~~~
jballanc
"The only bug free code is the code you don't write."

~~~
jandrese
If you think the third party library is bug free I have news for you. The only
difference is that you get to debug someone else's code instead, and you might
not have the source.

------
peter303
2016 is my 40th year of using C. I prefer Java.

------
dllthomas
My biggest tip for writing maintainable C for modern compilers is to avoid
passing bare primitives between functions. Don't just typedef - wrap things in
(single-element) structs that describe the contents. The compilers these days
understand that they can boil off the wrapper when generating code.

------
muricula
Is it better to fully qualify structs and unions or to hide their names by
typedefs? I frequently see people hide all struct types because it's visual
clutter and extra typing. In other words, given some struct a, should I do:
typedef struct a a_t;

I realize this is a bit of a style question, but I'm curious.

~~~
rcfox
Whichever way you go, you should not append _t to the end of the name. That's
reserved by POSIX.
[http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2...](http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_02)

------
cpeterso
clang has a -Weverything flag that, unlike "-Wall", will enable _all_ compiler
warnings. This includes possibly contradictory warnings. :) For new C code,
-Weverything is a good place to start.

Unlike gcc, clang does not have good documentation for its warning options.
The best sources I've found are the clang code itself
([https://github.com/llvm-
mirror/clang/blob/master/include/cla...](https://github.com/llvm-
mirror/clang/blob/master/include/clang/Basic/DiagnosticGroups.td)) and
[http://fuckingclangwarnings.com/](http://fuckingclangwarnings.com/).

------
legulere
I would add:

\- Add security measures provided by your compiler to your builds: -fstack-
protector-strong on GCC. -fsanitize=safestack on Clang if you have a version
that's new enough.

\- Build the program with AddressSanitizer and run it with typical data. Run
it through a fuzzer afterwards.

------
jhallenworld
C should have an inferred integer type:

for (infer x = 0; x != 10; ++x) ...

If the compiler can infer the type, use the fastest integer. If it can not
infer, use the largest integer.

I'm sure this would create difficulties with structs, but at least for local
integers it would be nice.

~~~
bluetomcat
Even if you use int in that case, the compiler is free to pick the optimal
underlying data type (byte, probably), provided that it has proven that the
value will never go out of that data type's limits and the program doesn't do
anything fancy like casting the int to void * and reading it byte by byte.

~~~
jhallenworld
Interesting, so it's a waste of time to use anything but intmax_t.

------
wyldfire
> For modern programs, you should #include <stdint.h> then use standard types.

Unless you're doing memory-mapped IO, or other very-specific things, you
probably only need the ___least_t or ___fast_t types (int16_least_t, e.g.).

------
admax88q
"How to C in 2016" Don't. C has outlived its usefulness. It's time to move on
to more expressive languages. The fact that buffer overflows are still a thing
in 2016 should be an embarrassment to the industry.

~~~
Gibbon1
Personal opinion, people are generally way too concerned about buffer
overflows in C. Meaning if you have issues with buffer overflows the root
cause is you are seriously writing C the wrong way circa 2016.

When I hear buffer overflows I think.

Programmer trying to be smarter than he is. Depreciated unsafe string
functions. Unnecessary pointer arithmetic. No unit tests Failing to use static
code analysis tools

~~~
admax88q
> Personal opinion, people are generally way too concerned about buffer
> overflows in C

My only concern about buffer overflows in C is that memory error continue to
happen in software that I depend upon in my daily life.

Chrome, Firefox, Linux, OpenSSL, all these things suffer memory errors that
compromise my security. Anyone doing security work in C in 2016 is in my
opinion committing malpractice and putting user's at risk because their ego's
can't take not fiddling bits by hand.

------
kardos
> Formatting

> The solution here is to always use an automated code formatter.

Spot on. Python got that part right.

~~~
kqr
Surely you mean Go? There is one for Python, but it's not official.

~~~
kardos
Python won't run without specified indentation, and leaves no freedom for
brace positioning. On the C side, we've accumulated a variety of formatting
styles [1].

[1]
[https://en.wikipedia.org/wiki/Indent_style#Styles](https://en.wikipedia.org/wiki/Indent_style#Styles)

~~~
yarrel
You can automatically format C.

Python, not so much.

~~~
kqr
Both of you make the mistake of confusing formatting with indentation.
Indentation is just a tiny part of formatting. There are tools to
automatically format Python (but they do obviously require that the
indentation is somewhat correct before you run them) and they work fairly
well. :)

------
ape4
> It's important to not remain stuck in your "things I learned in the 80s/90s"
> mindset of C development.

Instead use C99 ... which is from 1999. (Yes, I know most people didn't learn
it in the 90s)

~~~
viraptor
That's a bit unfair. The standard is from '99, but the implementation happened
differently than c11 (which already had many bits implemented in clang before
it was released).

In practice a lot of c99 got into gcc after 3.0 (2001) and it still took a bit
of time for any distribution to include it as stable. If I remember correctly,
lots of distributions held on to 2.9x for as long as possible. I'd say
realistic c99 support has ~10 years now, rather than 17.

------
lephyrius
"generally you want -O2"

I see this a lot shouldn't O3 produce better optimizations? Is that people
have large codebases? OR is it that O3 does some CPU/memory tradeoffs? OR
something else?

~~~
michael_h
Sometimes -O3 produces slower code than -O2. Sometimes it produces incorrect
code.

I usually compile with -O2 and annotate certain functions with O3 - if it
looks like vectorization will give the function a boost.

~~~
ahoka
To be honest I don't understand why isn't -O2 the default. Also does anyone
ever use -O1?!

~~~
astrange
-O1 is good for debugger compatibility, and for avoiding compiler bugs at higher levels.

------
caf
The article seems to be implying that you should use both -O2 and -Os, but
these are mutually exclusive options - if you supply both, only the last one
specified has effect.

------
jhallenworld
-Wextra is not enough. Should also have: -Wconversion and -Wstrict-overflow=4

In some ways -Wextra is too much: it's annoying that -Wall with -Wextra
complain about unused parameters.

------
asveikau
Never use int? Use uint8_t instead of char for strings? These are not best
practices, this is a lot of terrible advice that will make many c people laugh
at you.

------
cptaffe
> #define mycalloc(N) calloc(1, N)

I am not a fan of the use of macros here. You should never use macros.

Instead maybe: static inline void *mycalloc(size_t sz) { return calloc(1, sz);
}

~~~
ryandrake
Why would you generally need to wrap calloc inside a macro or function? Just
call calloc(1,sz)

~~~
pconner
Over years of development, this wrapper can save tens of seconds of time!

------
aback
> There's not much room for "casual observer C development." For the rest of
> the world, that's why we have Erlang.

lmaoed at that one

------
typon
C Programmers bragging about writing good C in this thread is just funny. A
large majority of new code does NOT need to be written in C. There's nothing
extra-ordinary about knowing how to write C. Learn to write HDL if you want to
brag.

------
ChuckMcM
Since I like coding in C, I am mildly offended by the notion that you should
not write code in it, but I really enjoyed that all of these things were put
together in one place.

------
kenOfYugen

      > Standard c99
    

If you have to write C, why not go with ANSI C/C89 for
compatibility/portability reasons?

~~~
_kst_
Because the 1999 and 2011 standards added some useful features. Unfortunately,
there are still some compilers that don't support C99.

------
atemerev
Oh my goodness, so true.

------
jrcii
Learning C is intimidating to me because every time I see a post written by a
long-time C programmer, there are 10 more long-time C programmers explaining
how the post is incorrect. Most C books on Amazon have reviews wherein
credible-sounding programmers criticize the mistakes of the author.

If publishers and programmers with popular blogs can't get a consensus on what
constitutes correct C, how could I ever hope to produce it?

Case in point
[https://news.ycombinator.com/item?id=10864601](https://news.ycombinator.com/item?id=10864601)

~~~
greydius
This is a valid observation. Setting aside all the technical flaws with the C
language, the fact that most supposed experts can't agree on best practices
for even the most trivial usage details does not inspire (for me, at least)
much confidence that it would be a good language choice.

------
ue_
>If you find yourself typing char or int or short or long or unsigned into new
code, you're doing it wrong.

int is a type that's at least 16 bits large. I can't see the problem with
using it. If I specify that I want a type that's 32 bits large, my code won't
run on 16 bit systems.

Why should I assume? Because the author doesn't believe in writing code that
works on "20 year old systems"?

~~~
CrLf
Don't agree with that either.

IMHO, "int" is perfectly acceptable if you just want to use the platform's
preferred word size (and are fine with the possibility of it being only
16bit). Also, "char" is perfectly acceptable too if you're dealing with ASCII.
This is valid for both the signed and unsigned variants.

The general rule should be using the language types as long as you're not
assuming something about their size, only then use "stdint.h" because it makes
your assumption more explicit.

Now, nobody uses "short" or "long" to mean "something smaller or shorter than
the platform's int", so these are usually something to avoid.

~~~
AndyKelley
Is "the platform's preferred word size" meaningful? On x86_64, pointers are 8
bytes, but it's still faster to use smaller registers if you don't need the
extra bits. For this system, at least, the correct type is the smallest one
that is big enough to hold the maximum value your integer could be.

~~~
mpu
Bullshit, on x64 all 32 or 64 bit base arithmetic operations have the exact
same cost, except the 64 bit version is one byte longer because of a REX
prefix. This is why 32 bits is the natural int size for x64.

~~~
vardump
Well, at least division isn't. On reasonable recent Intel Broadwell, signed
64-bit division (IDIV) takes 59 cycles, while signed 32-bit division takes
just 9 cycles.

Granted, divisions are rare, but that's still nearly an order of magnitude
difference in that corner case.

32-bit and 64-bit addition, multiplication, comparison, logical operations
etc. are in almost all cases just as fast.

You'll still need more storage on the stack for 64-bit variables. So more
cache misses (and perhaps TLB misses and page faults) when corresponding
boundary is crossed.

------
SFjulie1
Advice forgotten: \- you should always avoid library whose API documentation
are unaccessible or poorly written; \- unix man page is a great format but
windows has good help format too (synopsis, signature, use case, error
handling, meaning of constants, caveats), \- always check every function you
are using from time to time ... human memory is not perfect, even open/calloc
....; \- don't copy paste example code blindly from man or stackoverflow ...
(I remember windows C++ doc having horrible example with bugs);

Have a reference to the norm easily accessible and don't use options of the
compiler which impact you don't fully know.

Most developer are poorly understanding multithreading and it can be tricky to
make a portable library that has this property. Don't hesitate to stipulate in
your documentation that you did not cared about it, people can then use
languages with GIL (ruby, python ...) to safely overcome this issue.

Modern language are social, take advantage of sociability (nanomsg, swift,
extension, #include "whatever_interpreter.h" ....).

Programming in C with dynamic structures is guaranteed to be like a blind man
walking in a mine field: you totally have the right either to make C extension
to modern language OR include stuff like python.h and use python data
structure managed by the garbage collector from C.

Old style pre allocated arrays may not be elegant, but that's how critical
system avoid a lot of problems.

DO NOT USE MAGIC NUMBERS...

USE GOTO for resource cleaning on error and make the label of the goto
indentend at EXACTLY the same level the goto was written, especially when you
have a resources that are coupled (ex: a file and a socket). Coupling should
be avoid, but sometimes it cannot. Remember Djikstra is not that smart, he was
unable to be a fully pledged physicist that can deal with the real world.

Avoiding the use of global variable is nice, but some are required, don't fall
for the academic bias of hiding them to make your code look like it has none.

Copy pasting function is not always stupid (especially if you use only 1
clearly understandable function out of a huge library).

Be critical: some POSIX abstraction like threads seems to be broken by
complexity: KISS.

Modern C compiler are less and less deterministic, learn about C undefined
behaviour and avoid clang or GNU specific optimisations and CHECK your code
gives the same results with at least 2 compilers.

Don't follow advices blindly: there are some C capos out there that dream an
ideal world. Code with your past errors in mind and rely on your nightmarish
past experiences to guide you to avoid traps. Feeling miserable and
incompetent is some time good.

Resources are limited : ALWAYS check the results of resource exhaustion in C
instead of relying on systems and always have error messages preallocated and
preferably don't i18n them. Don't trust the OS to do stuff for you (closing
files, SIGSEV/SEGFAULT ...).

KISS KISS KISS complexity is your enemy in C much more than in other
languages. Modern C/C++ want you to build complex architecture on libraries
that are often bloated (glibc).

Egyptians are nice looking.

Once you finished coding you have done only the easy part in C: \- dependency
management is hard in C; \- have a simple deterministic build chain; \- you
should write your man pages too; \- you should aim at writing portable code;
\- static code analysis, maybe fuzzing, getting rid of all warnings is the
last important part.

Coding in C is just about 15% of the time required to make great software. You
will probably end up debugging it much more than you write it: write code for
maintainability as your first priority and do not under any circumstances
think that you can handle multiple priorities (memory use, speed, ...).

Above all : just write maintainable code, please. This rule is the golden rule
of C and it has not changed.

~~~
klodolph
I like this better than 95% of what's written in this thread (and better than
the original post). My favorite part is "don't follow advices [sic] blindly".
Fresh college graduates have a peculiar way of damaging code when they follow
best practices without the experiences to back them.

I would add one thing.

Know your target. Batch programs, daemons, microcontroller programs, cross-
platform programs are all different and you're going to end up with different
styles.

~~~
SFjulie1
Edit: thank you.

Well that is the difference between writing code and bringing a solution that
fits a problem.

I fully agree.

But IT has become shove sellers to other shove seller hoping for a new gold
rush to happen.

Computers have still not proven to be an actual improvement to the economy.
Positive impact cannot be measured. I guess I know why.

It looks like the lost bet of Diesel to empower the poorest to have access to
the economy and fight equally with capitalists able to buy steam machine
thanks to the banks: IP laws makes it impossible for a new economy to rise.

Those who do not learn from the errors of the past are doomed to reproduce
them.

------
Kenji
Wow, I wish I knew all of this when I started a big project last year... It's
kinda hard to change it NOW. And with that I mean it would be endless
rewriting of the entire thing.

------
J_Darnley
> You continue arguing, "on a 32 bit patform I want 32 bit longs and on a 64
> bit platform I want 64 bit longs!"

Ha ha ha. What kind of programmer would argue this? Do they ignore Windows 64?
The type long is 32 bits there.

------
shusx1234
a rubbishy article

------
admax88q
"How to C in 2016" Don't.

------
plg
"Notice we don't have char anymore. char is actually misnamed and misused in
C."

so defining a string happens how if not:

char *my_string = "Hello";

~~~
efaref
That's not even correct. The type of string literals is "const char *".

~~~
plg
i didn't mean a const

i meant a variable

~~~
julian-klode
Your variable my_string is a pointer to a constant string.

char * my_string

It must be a const char * my_string You can still assign a different string to
that, as my_string is not const.

For my_string to be const, you'd have to use char * const my_string

Now the location pointed to by my_string is writable but I cannot change the
location.

To make it entirely unmodifiable, you'd use const char * const my_string.

On the other hand, it might make more sense to use an array here.

const char my_string[] = "hello";

------
kazinator
> _Standard C99_

No thanks; sticking to C90.

> _LTO_

Violates ISO C, which says that semantic analysis is done in translation phase
7, and only linking takes place translation phase 8.

~~~
izacus
You could at least add some information about "why" for both of those :/

~~~
kazinator
The "why" for the second one is: that's the model of the C language. Programs
are translated in units. The semantic analysis ends when a translation unit is
analyzed. All that is left is to resolve the external references when multiple
units are linked; nothing is re-translated. This is very clearly stated in a
few sentences in that section of the standard which describes the translation
phases one by one.

Link-time optimization is a language extension which alters the concept of a
translation unit. In some ways, multiple translation units become one unit
(such as: they inline each other's code, or code in one is altered based on
the behavior of code in the other). But in other ways they aren't one
translation unit (e.g. their file scope static names are still mutually
invisible.)

One reason I don't use C99 is that many of its features aren't C++ compatible.
Most of C90 is. With just a little effort, a C90 program compiles as C++ and
behaves the same way, and no significant features of C90 have to be wholly
excluded from use.

Portability to C++ increases the exposure of the program to more compilers,
with better safety and better diagnostics.

In some cases, I can use constructs that are specially targetted to C or C++
via macros. Instead of a C cast, I can have convert(type, val) macro which
uses the C++ static_cast when compiled as C++, a coerce(type, val) which uses
reinterpret_cast, or strip_qual(type, val) which uses const_cast. I get the
diagnostics under C++, but the code still targets C. I'm also prevented from
converting from void * without a cast, and there are other benefits like
detecting duplicate external object names.

This way of programming in C and C++ was dubbed "Clean C" by the Harbison and
Steele reference manual.

C90 is a cleaner, smaller language than C99 without extraneous "garbage
features" like variable-length arrays that allocate arbitrary amounts of
storage on the stack without any way to indicate failure to do so.

The useful library extensions in C99 like snprintf or newer math functions can
be used from C90; you can detect them in a configure script like any library.

The overall main reason is that I simply don't have faith in the ISO C
process. The last good standard that was put out was C90. After that, I don't
agree with the direction it has been taking. Not _all_ of it is bad, but we
should go back to C90 and start a better fork, with better people.

    
    
       git checkout -b c90-fork c90
       git cherry-pick ... what is good from c99-branch

~~~
qb45
Aren't you just nitpicking on LTO? Who cares what the linker does if it
preserves semantics? Does it actually break some real-world code?

~~~
kazinator
It absolutely does. For instance, it can enforce strict aliasing across
translation unit boundaries, breaking code which depends on that separation to
protect it.

------
forrestthewoods
"The first rule of C is don't write C if you can avoid it."

Eek! Not what I was expecting. In the video game world there is a not so small
contingent of "write everything in C unless you have a really damn good reason
not to".

Great post though. Now go forth into the world and write C, my friends!

