💾 Archived View for koyu.space › aartaka › public › making-c-prettier.gmi captured on 2024-05-10 at 10:48:56. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2024-05-26)

-=-=-=-=-=-=-

đź“Ž Making C Code Prettier

By Artyom Bologov

<figure>

"A village landscape with people butchering animals and otherwise engaging in harvest activities"

<figcaption>

Picking up fruit of good C programming. "Autumn" by Pieter van der Heyden, 1570

</figcaption>

</figure>

C has a bad rap for being unreadable.

I've already explored

the ways it can get worse,

so this is a sequel complementing the original post.

I'm going through the ways one can make their C code

more readable and modern-looking.

Test Program (#test-program)

This time, I don't have a piece of code to experiment on.

Most of the examples are taken from Suckless software and redacted for readability.

So yes, this post is a bit of appreciation for the community work.

Now, to the exact prettiness hacks.

Good Code Style (#style)

I know I listed indentation styles as ugly

in the previous post.

But that's the whole point of indentation styles: making the code more readable.

Some styles look ugly but accomplish a certain goal nonetheless.

I'm using a Linux Kernel Style in my projects, because it looks good and promotes good practices.

But you can always call me out as wrong.

Non-Bracketed Blocks (pre-ANSI) (#non-bracketed-blocks)

This one is pretty obvious: you can omit the curly braces marking block start/end.

In most control structures: if/else, for, do/while.

Suckless software utilizes that for increased readability and brevity.

void
tdefutf8(char ascii)
{
    if (ascii == 'G')
         term.mode |= MODE_UTF8;
    else if (ascii == '@')
         term.mode &= ~MODE_UTF8;
}

Looks quite Pythonic to me. Do we need Python if we have this in C?

More Readable Types and Constants (C99) (#types-and-constants)

Missing true/false when dealing with boolean data?

Not sure how may bytes a certain value occupies?

Like how Rust did with <code>u32</code> and <code>i64</code>?

C99 headers—

stdbool.h

for booleans,

stdint.h

for fixed width types—to the rescue!

typedef struct {
    Rune u;
    ushort mode;
    uint32_t fg;
    uint32_t bg;
} Glyph;

There are more types in this snippet—ushort, Rune, Glyph.

But these are Suckless types.

Still, sane typedefs make your code more readable.

Well, if you're considerate enough.

Compound (C99) and Anonymous (C11) Initializers/Literals (#compound-anonymous-initializers-literals)

Python and JavaScript have a readable literal object syntax:

{key1: value1, key2: value2}
[elem1, elem2, elem3]

C has more primitive data structures,

but there are literal initializers that share some of this readability.

// Structure initializers:
{value1, value2}
{.key1 = value1, .key2 = value2}

// Array initializers:
{elem1, elem2, elem3}
{[0] = elem1, [1] = elem2, [2] = elem3}

Here's initializer-based Surf config snippet (actually part of my setup)

that shows why Suckless software is so readable and configurable:

static Parameter defconfig[ParameterLast] = {
    /* parameter                    Arg value       priority */
    [AccessMicrophone] = { { .i = 0 }, },
    [AccessWebcam] = { { .i = 0 }, },
    [Certificate] = { { .i = 0 }, },
    [CaretBrowsing] = { { .i = 1 }, },
    [CookiePolicies] = { { .v = "@" }, },
    [DarkMode] = { { .i = 1 }, },
    [DefaultCharset] = { { .v = "UTF-8" }, },
    [DiskCache] = { { .i = 1 }, },
    [DNSPrefetch] = { { .i = 0 }, },
    /* ... */
    [MediaManualPlay] = { { .i = 1 }, },
    [PreferredLanguages] = { { .v = (char *[]){ "en_US" } }, },
    /* ... */
    [WebGL] = { { .i = 0 }, },
    [ZoomLevel] = { { .f = 1.3 }, },
};

There is also an option (since C11) to pass anonymous structures to functions, instead of allocating, passing, freeing the data just for one call.

Saves you a couple of lines and many use-after-free errors.

Generic Dispatch (C11) and Type Inference (C23) (#"generic-dispatch-type-inference")

C is statically typed and you have to provide types for everything.

And it's overly verbose and uncomfortable to write programs in.

Right? There actually is generic dispatch in C11

 #define print_val (val) _Generic((val), int: printf("%i\n", val), char*: puts(val))

And there is type inference starting from C23:

 #define iter (times) for(typeof(times) i = 0; i < times; ++i)

That's a useful example for

typeof operator:

inferring some macro argument type.

But there's a more useful and immediate side to it,

like Go's

:= and C&num; var: <code>auto</code>.

auto i = 3;

Conclusion (#conclusion)

While this is nowhere close to the full listing of quality of life features,

I'll stop here.

The main point is delivered: C might be quite readable,

and there's a whole software ecosystem exploiting it—Suckless.

In case you have an impression that C is undecypherable,

you might enjoy checking out modern C.

Especially with the

awesome changes introduced with C23!

This website is

Designed to Last

and generated with the help of

C preprocessor.

You can view page sources by appending .h to the page URL.

Copyright 2022-2024 Artyom Bologov (aartaka).

Any and all opinions listed here are my own and not representative of my employers; future, past and present.

Back to home page

About me

My projects