
One year of C - gok
http://floooh.github.io/2018/06/02/one-year-of-c.html
======
blub
Redescovering the beauty of C seems quite similar to city-dwellers
redescovering the beauty of nature: everything is rainbows and butterflies,
while in reality - if you are away from the safety net of civilisation - there
are hundreds of ways nature will crush you if you make even a small mistake.

~~~
skummetmaelk
And then you have the rugged outdoorsmen get serious about nature and
venturing into the wilderness. They plan for the worst. They bring enough
supplies. They don't try to climb mountains in flip flops. They shake their
fists at the city touristers leaving trash at the campsite and causing memory
leaks. No garbage collectors out here. They know to respect the ecosystem
flora and fauna or get ripped to pieces.

~~~
blobjectivist
and then there's the guy whose back in the city who knows the world is
designed by aliens and that nature is artifice, all decent processor
architectures are designed using dataflow HDL languages and the notion of
"bare metal" nature is a delusion held by hipsters and unix greybeards

~~~
z3phyr
But god created in nature three basic elements (resistance, capacitance and
inductance) and with holy will and power forged the foundations. The wisdom of
the first borns say there are more basic elemental that god intended
(memristors...)

~~~
henrikeh
But even these are approximations and false gods.

Electromagnetism and solid-state physics are more fundamental still.

------
jcelerier
> no actions need to happen on destruction

so... you like never free, never fclose, never munmap, never pthread_join,
never unlock mutexes, never close network connections or what ?

Also, all of your protoypes such as

    
    
        sg_pipeline sg_alloc_pipeline() {
    

should really be

    
    
        sg_pipeline sg_alloc_pipeline(void) {
    

since the first one can actually be called with any number of arguments (in C,
not in C++).

Note also that C doesn't have return-value-optimization, hence all your
struct-returning functions can possibly cause a call to memcpy (won't happen
when compiled in C++ mode of course) and will generally lead to much more
binary bloat than the traditional C way of passing outputs as arguments. e.g.
given this trivial code in foo.c:

    
    
        typedef struct _foo
        {
            int x;
            float foo, bar;
            char z[1024]; // change this to 10024 to get a memcpy! 
        
        } foo;
        
        void do_stuff_to_foo(foo*);
        
        foo blah()
        {
            foo f;
            do_stuff_to_foo(&f);
            return f;
        }
    

A disassembly after compilation with gcc -std=c99 -O3 gives

    
    
        blah:
        .LFB0:
    	pushq	%rbp
    	movq	%rdi, %rbp
    	pushq	%rbx
    	subq	$1064, %rsp
    	movq	%fs:40, %rax
    	movq	%rax, 1048(%rsp)
    	xorl	%eax, %eax
    	movq	%rsp, %rbx
    	movq	%rbx, %rdi
    	call	do_stuff_to_foo@PLT
    	movq	(%rsp), %rax
    	leaq	8(%rbp), %rdi
    	movq	%rbp, %rcx
    	andq	$-8, %rdi
    	movq	%rbx, %rsi
    	subq	%rdi, %rcx
    	movq	%rax, 0(%rbp)
    	movq	1028(%rbx), %rax
    	subq	%rcx, %rsi
    	addl	$1036, %ecx
    	movq	%rax, 1028(%rbp)
    	shrl	$3, %ecx
    	rep movsq
    	movq	1048(%rsp), %rdx
    	xorq	%fs:40, %rdx
    	jne	.L5
    	addq	$1064, %rsp
    	movq	%rbp, %rax
    	popq	%rbx
    	popq	%rbp
    	ret
    
    

The same thing built with g++ -O3 gives

    
    
        _Z4blahv:
    	pushq	%rbx
    	movq	%rdi, %rbx
    	call	_Z15do_stuff_to_fooP4_foo@PLT
    	movq	%rbx, %rax
    	popq	%rbx
    	ret
    

While clang (in C mode) goes for a memcpy:

    
    
        blah:                                   # @blah
    	pushq	%r14
    	pushq	%rbx
    	subq	$1048, %rsp             # imm = 0x418
    	movq	%rdi, %rbx
    	movq	%fs:40, %rax
    	movq	%rax, 1040(%rsp)
    	movq	%rsp, %r14
    	movq	%r14, %rdi
    	callq	do_stuff_to_foo@PLT
    	movl	$1036, %edx             # imm = 0x40C
    	movq	%rbx, %rdi
    	movq	%r14, %rsi
    	callq	memcpy@PLT
    	movq	%fs:40, %rax
    	cmpq	1040(%rsp), %rax
    	jne	.LBB0_2
    	movq	%rbx, %rax
    	addq	$1048, %rsp             # imm = 0x418
    	popq	%rbx
    	popq	%r14
    	retq

~~~
obl
> Note also that C doesn't have return-value-optimization, hence all your
> struct-returning functions will cause a call to memcpy (won't happen when
> compiled in C++ mode of course).

What ?

RVO is precisely needed because a copy in C++ can run arbitrary code and so is
not as easy to ellide as a memcpy. RVO is basically a promess you make to have
your copy constructor & destructor be semantically harmless compared to a
memcpy.

There is no need for RVO in C.

~~~
BeeOnRope
That was exactly my thought as well, but the examples seems to show otherwise
(at least on gcc and clang[1]).

The compilers are using basically the same underling optimizer and back-end
with different front-ends, and since in C there are no "user-defined
constructors" and no destructors, one would expect that you don't need any
special RVO rule in C: the compiler can simply observe that a local object is
returned and construct it in-place as necessary.

Thinking about this example, this may not be the case: distinct objects have
to have distinct addresses, right? So in C you might not be able to make this
optimization since the do_stuff_to_foo method (a black box to the compiler)
could save its argument, and the caller of blah() could see that the argument
it passed has the same address as the local f object in blah, a violation of
"distinct objects, distinct addresses".

C++ has a the RVO escape hatch for this: it is expected that some objects that
appear distinct in the source may not actually be distinct if they fit the RVO
(or NVRO) pattern - but C does not. So perhaps gcc and clang are doing the
right there here.

\---

[1] All numbered versions of clang up to 6.0 seem to behave the way indicated
in the GP post, but trunk in godbolt, which shows version as 7.0.0 (trunk
333657) compiles C efficiently like C++.

~~~
obl
very good point on the "addresses compare == iff same object" rule.

In that case though, I think clang is right to optimize the callee (but it
does introduce a problem in the caller) :

the only place you could do the equality check and observe the rule being
broken is before the callee returns since the lifetime of its variable is
bound to the call.

It seems that clang will not let the return pointer alias a local in the
caller except when the call is the initialization of said local.

So if the caller goes :

foo x; leak(&x); x = returns_foo();

the memory will be temporary stack (and then memcpy), thus upholding the rule.
(and it seems to me that this inefficiency is really required to respect the
standard if we actually leak the pointer)

in the case :

foo x = returns_foo();

clang will pass the actual address of x down but that's before the object
exists (and its address cannot be known yet) so the rule is still fine.

I stand corrected though, this does mean that RVO would be useful for C as
well, as a way to relax the aliasing rule.

edit: nevermind that, in the first case it's perfectly legal to read/write foo
through the pointer downstream so you cannot make the optimization anyway.

~~~
BeeOnRope
Yes, the same thought occurred to me (that perhaps clang is careful in the
caller in the case the address escapes), but I seemed to find cases where
clang optimizes the caller also, so that two distinct objects receive the same
pointer and both pointers escape.

Here's an example:

[https://godbolt.org/g/yxFzqT](https://godbolt.org/g/yxFzqT)

This happens on clang versions back 3.6.

Note that if you change the caller to:

    
    
        Foo f;
        f = callee(&f);
    

the code changes and distinct objects are passed. I'm not sure if the first
form (all in the definition) has a relevant difference per the standard that
lets clang do this.

------
nevi-me
The author makes an important point about having a list of languages to choose
from.

We are working on a JVM-based project at work. We chose to use Kotlin because
the initial team was intermediate programmers who didn't have that much
experience. When we got an MVP out, and some clients willing to pay for the
solution; we got some Java devs involved.

The guys have only known Java 6 and 7. They are struggling around with Kotlin,
not because it's difficult, but because they only know "The Java Way". Sadly
also, they've been stuck in J6/7 for so long that they don't know Streams or
Lambdas. For people making their living from Java, that's dangerous self-
extinction.

Their software architect is even worse. Ignorant guy who lives in a rock, if
they don't understand something or have never heard of it, it's wrong or
useless.

We're working on a greenfield project, and the guy wanted to write a REST
proxy on top of gRPC because he thinks clients won't like us having ports
exposed internally.

I nearly took him for his salary when I told him that *://localhost is only
visible to the local machine. He bet me his salary that he could access a
service on the server even if it's listening on localhost. I was gracious when
it came time to pay up.

Anyways, I wish the guys were the exception, but I've seen many guys in their
30's who still think Oracle Inc. is where the sauce is.

My advice: learn something new every once in a while. The author found that C
was better suited for some things than C++. That's a powerful statement to
make, something I've learnt a lot over the years when I pick up a new
language.

~~~
pjmlp
You got a point, but being stuck into older language versions is quite common
on enterprise shops.

If the server they have to take care of only runs language version X, that is
what they care about, after work there is another life totally away from
computers.

When time comes for language X + 1 version to be replaced on the deployment
system, then there will be one week of web based trainings to learn whatever
is new in version now available on the IT development images and that is it.

Yes, there are lots of modern companies out there, but there are tons of them,
specially when software is not their core business where development
improvements come at snail speed, if at all.

------
kakwa_
> The dangers of pointers and explicit memory management are overrated

Hummm, not my experience. The first time I ran AFL on one of my C library
(parser/converter of EMF files, really easy to mess up), the result was quite
frightening. IIRC, it took only 20 seconds for AFL to find 10 crashes.

Ever since, first I'm more careful, second I run AFL from time to time, and it
happens regularly that it catches some edge cases I was not able to see.

I love programing in C, but I definitively know it's easy to make mistakes in
it.

~~~
phendrenad2
What's AFL?

~~~
kakwa_
American Fuzzy Lop.

[http://lcamtuf.coredump.cx/afl/](http://lcamtuf.coredump.cx/afl/)

And no, it's not the rabbit ^^.

It's an 'intelligent' (as opposed to purely random) fuzzer that is quite good
at finding edge cases (it's a very/too short summery, please read the link for
more details).

------
Philipp__
I like this blogpost. But as stated in it, “my project”. Writing C project,
alone is pretty different than writing C with 1 or more persons.

In highschool we had group project. Writing simple tcp/ip protocol in C. Holy
shit did I see some awfull things... maybe writing C is not the _bad_ thing
about it. But maintaining could be.

Simple example, you can read string from simple data file on 6,7 different
ways. Yet their effects and behaviors differ severely. Not everyone knows
difference in parameters and returns in scanf, fgets and gets...

~~~
loeg
I think your experience writing C with a group might be severely colored by
the group being made up of inexperienced highschoolers.

~~~
Philipp__
I totally agree with you on that. But the whole point was that there is much
certain possibility for things to go wrong with inexperienced developers. I
won’t quote the comment in the reply below, but that is the thing...

edit: typing on the phone, sorry

~~~
loeg
In my experience, turning an inexperienced C developer loose with C++ does not
result in an improvement :-).

------
strkek
> btw, did you notice how there are hardly any books, conferences or
> discussions about C despite being a fairly popular language?

Oh, yes. When I wanted to fiddle with OpenGL (and later SDL), I was baffled to
find that absolutely _everything_ documentation-wise was for C++.

EDIT: Sorry, "documentation" was probably the wrong word to use here. I meant
"learning resources".

~~~
Sir_Cmpwn
Hum. That doesn't make sense, the OpenGL API is entirely C oriented and I
don't know of any C++ docs.

~~~
seandougall
It doesn’t make sense, but it jibes with my experience. Tutorials and blog
posts about OpenGL are super heavy on C++, because gaming.

The official docs are C, of course, because the API is C. But the docs are
rather abstruse for a beginner, and really become useful only well after
you’ve gotten your bearings. (e.g. my personal beef: function names tend to be
two nouns with no verb to say what the thing actually does, so you look in the
docs and it says it “binds” noun A and noun B, with no explanation of what
“binds” means in context, which direction the data binding goes, whether it
persists, etc.)

------
richard_shelton
I have more uses for Python than the author, but one of these uses is really
obvious: Python as C code generator. Python + C is a very powerful
combination: metaprogramming, special constructs etc. Basically, with Python
you can get rid of preprocessor hacks and messing with templates.

~~~
flohofwoe
Ah, right! I actually use python for code generation as well, just forgot to
mention it. For instance the Z80 and 6592 CPU emulators use generated code for
the instruction decoder:

[https://github.com/floooh/chips/tree/master/codegen](https://github.com/floooh/chips/tree/master/codegen)

------
sriram_malhar
As someone said, "C is an improvement on its successors"

~~~
fanf2
Tony Hoare, “hints on programming language design”:

>> The more I ponder the principles of language design, and the techniques
which put them into practice, the more is my amazement and admiration of
ALGOL60. Here is a language so far ahead of itst ime, that it was not only an
improvement on its predecessors, but also on nearly all its successors. <<

[http://flint.cs.yale.edu/cs428/doc/HintsPL.pdf](http://flint.cs.yale.edu/cs428/doc/HintsPL.pdf)

~~~
sriram_malhar
Ah, nice. Thank you. I knew I had seen it somewhere.

------
Entalpi
C is beautiful because it is so barebones. It does not hide data behind
complex language feature that describe some runtime behaviour it is just bits
and bytes moving around. A bit of a romanticed view but that is atleast the
feeling I got when writing some small games witg C.

Link: [https://github.com/Entalpi/PongC](https://github.com/Entalpi/PongC)

------
bluescarni
> As a C++ programmer I developed my own pet-coding-patterns and bad
> behaviours (e.g. make methods or destructors virtual even if not needed,
> create objects on the heap and manage them through smart pointers even if
> not needed, add a full set of constructors or copy-operators, even when
> objects weren’t copied anywhere, and so on)

Yikes.

~~~
blt
I guess you are getting down voted for lack of content, but I agree. Those are
all very bad "Java without GC" C++ practices that lead to awful code bases.
Thankfully the author seems to have learned that from their experience writing
C.

Like the author mentions, C++ leads to overthinking which language features to
use. It invites you to seek these perfect abstractions for Python readability
with C performance. I think this is a big reason everyone develops their own
incompatible style -- not due to passionate philosophical beliefs, just to
reduce the size of the solution space.

~~~
pmarcelll
I agree with you, mainly from the perspective of writing Rust code. When Rust
was new, a lot of people said that thinking about ownership all the time is a
burden, but I think it's the opposite, it gives a sort of frame that helps to
build the software's core abstractions. I think in this regard C++ is much
more unopinionated, which means every codebase is different. In Rust,
ownership is part of the type system, so a lot of the times learning a new API
can be much faster because API usage is easier to understand and this style of
programming is already familiar.

~~~
blt
yes. ownership is an extremely critical part of software design, especially
for complex interactive programs.

------
pmarcelll
I thought when people say memory safety in C is a problem, they usually mean
in a larger codebase with hard to define API boundaries and with multiple
programmers that can't handle the complexity of the code, which results in a
team that can't find a single person that is familiar with the whole codebase.

~~~
01001
As a C coder I find myself trampling over my own memory a lot.

Say you have a pointer somewhere that points to a struct, and then once every
10 million iterations of a set of 20 functions that pointer gets written over
by a string that lacks a NULL terminator occasionally. So your program crashes
in a completely random place that has nothing to do with the origin of the
bug. That's the problem with memory safety. But the lack of memory safety is
also very powerful, you can malloc a chunk of memory and then use it in
extremely creative ways, you really see some peoples genius shine when you
read their source code, in a way that I haven't been able to see with other
languages.

~~~
megous
This is soved by knowing the destination size and using functions that respect
that and never assume the size of an input will not be larger. (unless
proximity of something that would assure the size of input is close enough to
where you're making the assumption about it in code, but still you'd be taking
risks, especially if it's not your code, but some foreign library call)

It's the same with web programming. You always escape on the output, or just
avoid escaping by using proper API (el.textContent = 'something').

------
opcenter
Very cool read and a lot of your happy discoveries are similar to reasons why
I like coding in Go, especially your "Less Language Feature ‘Anxiety’"
section. No guessing about how to do something, there's generally only one way
to do something.

------
ianai
I’m not a professional coder atm, but I learned quite a lot from
“Understanding and Using C Pointers.” It’s weird knowing that I like to and
can code, but being employed as a sys admin. Pretty common I imagine, as well.

------
buserror
I made that 'reverse path' to C quite a while back, more than 10 years ago in
fact. for 20 years before that, I was C++, C++, and C++. Then I gradually
realised I was trying to slim down my use of the language, due to stuff I just
didn't want in the codebase I was working on.

Small details. Like well templates -- they were fantastically hard to track
and debug at the time, and really, did they actually provide anything in the
long run? Answer was... NO. they don't. Turns out it's a LOT easier to have 2
discrete functions _explicitly_ doing the same thing than using a template.
_anyone_ will understand the scope of each if they see them; The arguments
about "simplicity" doesn't exist once you get templates involved.

So yes, you can 'template' in C as well using the preprocessor, but it's still
ends up being MORE READABLE than a heap of scopeless C++.

One thing I was fond of in C++ were stacked based objects; ie, use the
constructor to save some state, and use the destructor to restore it. I think
it is a lovely concept, and I still miss it -- hover it's a concept 'new
hires' had problems with very often -- it was not as intuitive as it felt. It
turns out an _explicit_ save/restore is better to maintain. I wish it was
different, but that got THAT feature of C++ out of the window.

Anyway, long story short, I wrote zillions of lines of C++, and now I just do
C. I don't have to fight with the compilers every other year for instance.

Small silly example, for a million years (it felt like it!) I could write pure
virtual functions declarations as ..... int blah_blah_method(some cool
parameter) = NULL; -- it actually made SENSE. Then one day, someone decided it
couldn't possibly be NULL -- it _had_ to be zero as '0' or else, no compily.

^^ that is one just a silly old single example, but theres a dozen others
where your 2 years old codebase suddenly isn't compiling because someone
decide to shift the language under your feet.

THAT doesn't happen with C. C just _works_. yes, there's lots of syntax sugar
I WISH I would have been able to keep from C++, but ultimately, the
possibility to write a huge pile of bloat is _a lot harder_ in C than in C++.
Some idiot 'new hire' won't make a hash objects for 5 elements.

Some idiot colleague won't decide that the string creation class constructor
shouldn't take a reference, but a copy, making the app startup take 30 seconds
with ONE character '&' removal.

Anyway, rant over. Regardless of what the kids says, C is nice, C is lean, C
is a LOT easier to keep clean as long as the adult people using it are aware
of the sharp edges :-)

~~~
maccard
Your post is very ranty, i disagree with lots, but I think you’re flat out
wrong here:

> Turns out it's a LOT easier to have 2 discrete functions explicitly doing
> the same thing than using a template

How do you write containers? I tried following a series on implementing an
interpreter in c a few months ago, but gave up because 30% of the code was
implementing std::vector for different types.

Also, math routines definitely work better templated. Code duplication isn’t
always bad but if your methods are copy and pasted with different signatures,
that sucks. E.g. an interpolation method that works on double and floats.

> ome idiot colleague won't decide that the string creation class constructor
> shouldn't take a reference, but a copy, making the app startup take 30
> seconds with ONE character '&' removal.

This isn’t an argument against c++, this is an argument against your co
workers. The exact same argument could be made for C with removing two *
signs.

C has it’s fair share of gotchas thy you don’t refularly hit in c++ too -
malloc requiring sizeof, not checking realloc’s return values, goto hell for
resource cleanup, complicated lifetimes.. personally I’d rather have C++,
warts and all, than have to deal with the “simplicity” of reimplementing a
stack in every resource owner, and reimplementing vector/map for every type.

~~~
dottrap
For C vectors, look at stb's[1] stretchy_buffer[2] or klib's[3] kvec[4].

The basic concept of these is they use macros to specify the type/size of the
elements so you can have generic containers in C.

For hash's, you can also look at klib's khash [5]

[1] [https://github.com/nothings/stb](https://github.com/nothings/stb)

[2]
[https://github.com/attractivechaos/klib](https://github.com/attractivechaos/klib)

[3]
[https://github.com/nothings/stb/blob/master/stretchy_buffer....](https://github.com/nothings/stb/blob/master/stretchy_buffer.h)

[4]
[https://github.com/attractivechaos/klib/blob/master/kvec.h](https://github.com/attractivechaos/klib/blob/master/kvec.h)

[5]
[http://attractivechaos.github.io/klib/#Khash%3A%20generic%20...](http://attractivechaos.github.io/klib/#Khash%3A%20generic%20hash%20table)

~~~
maccard
> The basic concept of these is they use macros to specify the type/size of
> the elements so you can have generic containers in C.

I figured as much. As bad as templates are, I’d take templates over macros any
day, especially if I have to debug one.

~~~
jstimpfle
A 1-line macro is not bad. stb's stretchy_buffer is not bad, but it could do
with less macros. Take the ones from my own example that I linked elsewhere on
this page.

If you use macros "correctly" there is rarely any logic in there, and they are
decidedly not hard to debug. Macros let you do things that you could not do
otherwise, like getting information about the caller or the size of the
element type that is abstracted behind a void ptr.

C++ templates on the other hand - if you have considerable C++ experience you
will shy them. They drive compile times up insanely, and are actually very
difficult to debug, at least under "normal" usage.

Also, code bloat results after a few instanciations where a simple runtime
integer could be used to discriminate between different cases, but with shared
implementation code.

I would never again touch even the most basic C++ template, that is
std::vector. I prefer my simple 3 lines of macros.

------
Too
> _Writing C++ classes often involves writing constructors, destructors,
> assignment- and move-operators, sometimes setter- and getter-methods… and so
> on. This is so normal in C++ that I only really recognized this as a problem
> when I noticed that I didn’t do this in C._

POD containers don't ever need manually written copy/move/assign constructors
or destructors. In fact hardly anything except resource managing classes need
them (eg if you are implementing shared_ptr or mutex yourself). If you are
writing your own copy constructor without knowing why, your code is not only
needlessly bloated but also most likely broken. Follow "rule of zero" instead.

------
ufo
Does anyone know more examples of C APIs where the user is responsible for
allocating data and where all the functions only "borrow" pointers? Is getting
rid of malloc/free worth the loss of information hiding?

~~~
kccqzy
Pretty much the vast majority of the API out there? Only relatively new ones
like asprintf or getdelim do memory allocation for you.

I don't understand your remark about information hiding. How does the
difference between returning an owned pointer and borrowing a pointer concern
information hiding?

~~~
ufo
If your function operates with a pointer to a struct you don't need to put the
contents of the struct in the header file. You can simply declare the struct
with

    
    
        struct mystruct;
    

and then use `mystruct*` in the function prototypes. I think in C++ this is
known as the PIMPL pattern.

I was also asking more about user-defined libraries than about the standard
library...

~~~
frankzinger
Opaque types do not force you to do the allocation in the library.

my_lib.h:

    
    
      struct my_struct;
    
      size_t my_struct_size(void);
    
      #define MY_STRUCT_MAX_SIZE 512
      #define MY_STRUCT_VAR(name) \
        char name##_buf__[MY_STRUCT_MAX_SIZE]; \
        struct my_struct* name = name##_buf__
    
      void my_construct(struct my_struct*, int, float);
    

my_lib.c:

    
    
      struct my_struct {
        int i;
        float f;
      };
    
      size_t my_struct_size(void) {
        return sizeof(struct my_struct);
      }
    
      void my_construct(struct my_struct* ms, int i, float f) {
         ms->i = i;
         ms->f = f;
      }
    

user.c:

    
    
      // On the heap
      struct my_struct* ms = malloc(my_struct_size());
      my_construct(ms, 123, 456.0f);
    
      // On the stack
      MY_STRUCT_VAR(ms2);
      my_construct(ms2, 123, 456.0f);

~~~
ufo
Do you know of any libraries that do this?

When I tried it here GCC complained about `ms2 = ms2_buf__` being an
assignment between incompatible pointer types. That sounds like an easy way to
get into trouble and undefined behavior.

~~~
frankzinger
I just wanted to convey the idea so I didn't compile or check the code. But
now that you bring up UB, I would change the MY_STRUCT_VAR macro as follows:

    
    
      #define MY_STRUCT_VAR(name)                          \
          _Alignof(max_align_t)                            \
           unsigned char name##_buf__[MY_STRUCT_MAX_SIZE]; \
          struct my_struct* name = (struct my_struct*)name##_buf__
    

Ie, changed to unsigned char and set the buffer to have the maximum
(strictest) alignment. And added the cast.

It's a pretty widely-used technique and I have satisfied myself in the past
that it's not UB (ie, by checking the Standard). I am no language lawyer
though so don't take my word for it. The heap example, on the other hand, is
pretty much identical to what an allocation inside your library would look
like so feel free to disregard the stack example if you'd like.

One example of something based on the same principle is the 'sockaddr'
structure in the BSD sockets API. See
[https://en.wikipedia.org/wiki/Type_punning#Sockets_example](https://en.wikipedia.org/wiki/Type_punning#Sockets_example).

~~~
frankzinger

      _Alignof(max_align_t)
    

should be

    
    
      _Alignas(max_align_t)
    

or

    
    
      _Alignas(_Alignof(max_align_t))

------
slavik81
This could also be viewed as a comparison between two different styles of C++.

~~~
hawski
With C99 it's not that obvious. Compound literals are outside of C++ standard.
However GCC for example supports them, but a bit differently [0].

    
    
      $ cat foobar.c
      #include <stdio.h>
      
      struct foo {
      	int x;
      	int y;
      };
      
      int foobar(struct foo *f)
      {
      	return f->x + f->y;
      }
      
      int
      main(int argc, char *argv[]) {
      	(void)argc; (void)argv;
      
      	printf("%d\n", foobar(&(struct foo){10, 20}));
      	return 0;
      }
      $ gcc -Wall -Wcast-align -Wextra -pedantic -std=c99 foobar.c -o foobar && ./foobar
      30
      $ g++ -Wall -Wcast-align -Wextra -pedantic foobar.c -o foobar && ./foobar
      foobar.c: In function ‘int main(int, char**)’:
      foobar.c:17: warning: ISO C++ forbids compound-literals
      foobar.c:17: warning: taking address of temporary
      30
      $ # taking address of temporary means that it's undefined behavior
    

[0] [https://gcc.gnu.org/onlinedocs/gcc/Compound-
Literals.html](https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html)

~~~
slavik81
The stated he was only using the subset of C99 that could compile as C++.

------
slaymaker1907
How do you avoid the need for dynamically growing arrays? For instance, if you
are doing any sort of IO, you often don’t know the size of the input
beforehand. That either forces you to use malloc or do some strange low-level
manipulations of the stack.

~~~
jandrese
Or you read a fixed block at a time. This gets awkward if you need to stitch
together the end of the previous block with the new one but it does have the
advantage of being fast, especially if you fix the block size to the block
size of the underlying filesystem (or a multiple).

~~~
s_m_t
+1, maybe it is because I'm not programming the most complicated things in the
world, but almost all of my data structures are fixed size arrays (or are
stored in them). In my experience, dynamic memory management simply isn't
needed unless you are doing something very very complicated.

------
jstewartmobile
The wonderful thing about C is that bad coding can't hide. The programs blow
up early and loudly. In safer languages, the memory bombs go away through
garbage collection / safe pointers / borrow checking / etc, but all of the
uglier and quieter mistakes are still very much there.

Knuth and von Neumann took the same approach to PRNGs back in the early
days...

~~~
kqr
> The programs blow up early and loudly.

Loudly, sure. Early? I wish that was the case. Heartbleed and other high-
profile exploits rely on this sort of thing being able to pass unnoticed.

~~~
jstewartmobile
You got me, fair-and-square! Speaking holistically though, most of the
programs I _enjoy_ using on a daily basis were written in either C or C++.
Safer languages _should_ have a tremendous edge here, but the proof has not
made its way into the pudding.

Just think C works as a kind of "high-pass" filter for programmers--and is
_net_ positive to overall software quality. Memory issues? YMMV

------
IloveHN84
Why not C11 instead of C99? It brings even new better safety

------
irundebian
You can't learn C in an afternoon.

~~~
AlotOfReading
You can absolutely learn the syntax of C in an afternoon, or a few days at
worst, if you're not having to learn to all the other concepts interesting C
programs require. Much of the immense learning curve comes from th things that
are "outside C", like how to synthesize high level concepts from the lower
level primitives in the core language and system specific gotchas the language
doesn't shield you from.

~~~
zbentley
> You can absolutely learn the syntax of C in an afternoon, or a few days at
> worst

That depends on what you mean by "learn".

Learn it well enough to make something that works for you, on your
architecture, with your toolchain? Sure.

Learn it well enough to know which _combinations_ of the syntax you (thought
you fully) learned trigger undefined and wildly variable behavior in the wild
or when run with other libraries/build environments? Hell no. And bugs caused
by that stuff are distressingly common.

That's not a "system specific gotcha". It's a huge area of skill-requiring
behavior that arises directly _within_ the core syntax of the language.

Saying "you can learn C in a weekend" is like saying "you can learn to drive a
car in a weekend" in reference to learning how to maneuver a car alone in a
big empty parking lot. Technically true, but it'll leave you woefully
unprepared for operating that car the way almost everyone wants to: around
other cars, in more complicated contexts, subject to many more unpredictable
factors (other people).

~~~
chungy
"Learn C in a weekend" (or afternoon) is akin to getting accustomed to a new
model of car, _with prior driving experience_.

Each car has its own quirks, each programming language has its quirks.
Learning C from the background of already knowing how to program is like
driving a new Ford Focus off the lot and getting used to it.

Learning C++, on the other hand... is expecting you to pilot a jumbo jet after
your only experience was the Ford Focus.

~~~
kqr
> "Learn C in a weekend" (or afternoon) is akin to getting accustomed to a new
> model of car, _with prior driving experience_.

...in an empty parking lot, sure. C brings a whole host of old issues to the
table that you simply do not get to experience in other languages.

C is deceptively easy to think you know, but I have yet to see evidence of
someone actually using it responsibly.

------
yitchelle
>> So one thing seems to be clear: yes, it’s possible to write a non-trivial
amount of C code that does something useful without going mad

As an embedded engineer who had written many low level code, all I had was C
and assembly. I know that the higher level of abstraction you go, the amount
of languages available gets bigger and bigger.

I am rather bemused that he had this impression about C. I wonder how many
other tech folks has the same impression??

~~~
cypher543
> I wonder how many other tech folks has the same impression??

I do! I've worked with C, C++, Python, Java, Go, JavaScript, Ruby, and Pascal,
but for anything that isn't network-oriented or a simple CLI tool, I always
reach for C (sometimes a strict subset of C++). Its distinct lack of features
allows me to focus more on the problem at hand and less on the best of the
hundred different ways to do something in other languages. Go is the same way,
which is why I use it extensively for mid-level stuff like CLI tools and
servers.

~~~
kuschku
I've used C and Go quite a bit, and I find it a horror to write.

I end up duplicating tons of code - the exact same code copied hundreds of
times across the codebase, with every time slightly different changes made to
it.

How do you do custom data structures that need to parametrize over multiple
types without duplication in C or Go? How do you do sane error handling
without 90% of your code being if (...) { // handle error } or if err != nil {
return err } ?

I mean, just read [https://git.kuschku.de/justJanne/statsbot-
frontend/blob/mast...](https://git.kuschku.de/justJanne/statsbot-
frontend/blob/master/main.go#L175) as example. (It's a quickly hacked-together
project I wrote in about 10h)

~~~
fpoling
That code can be simplified a lot if the db object stores the error itself so
it can be checked only once after few calls, like what bufio.Scanner does.

~~~
kuschku
That database object is from
[https://golang.org/pkg/database/sql/](https://golang.org/pkg/database/sql/)
which is part of the standard library of go.

------
Froyoh
"It takes a lifetime to master C++"

~~~
waynecochran
Yeah when I see stuff like this I want to just go be a barista or something:
[https://stackoverflow.com/questions/13230480/what-does-
auto-...](https://stackoverflow.com/questions/13230480/what-does-auto-tell-us)

------
tbranyen
Why do some devs spell JavaScript and TypeScript with the incorrect casing?
Not worth their time? Toy languages that aren't to be respected? Not like the
rest of the post was riddled with typos to justify it.

~~~
jacquesm
But what did you think of the article?

------
dpc_pw
For a small, one person project you can use absolutely any language and feel
great about yourself and the language. It's like a stroll in a park - you can
do it in any shoes, or even barefoot.

Where language matters are thousands or million lines of code projects, with
multiple teams and people of varying experience and abilities, coming and
leaving. With stupidly short deadlines and changing business requirements.
With liabilities, support, maintenance, budget, time-to-market, etc.

Then it's like climbing Mt. Everest, in a snowstorm, with limited supplies and
low team morale.

This whole article is just "mom look, I can walk". I come to HN to hear a
"I've survived hell and here is how my shoes did" story.

