Making C Code Prettier

By Artyom Bologov
A village landscape with people butchering animals and otherwise engaging in harvest activities
Picking up fruit of good C programming. "Autumn" by Pieter van der Heyden, 1570

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

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

I know I listed indentation styles as ugly 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)

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;
}
A function from st using non-bracketed blocks

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

More Readable Types and Constants (C99)

Missing true/false when dealing with boolean data? Not sure how may bytes a certain value occupies? Like how Rust did with u32 and i64? 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;
Snippet with fixed-width types from st.h

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

Python and JavaScript have a readable literal object syntax:

{key1: value1, key2: value2}
[elem1, elem2, elem3]
JSON-like Python/JavaScript initialization syntax

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}
C99 compound initializers

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 },   },
};
Surf config snippet with compound array initializer (redacted for readability)

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)

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))
Example of generic dispatch from C11

And there is type inference starting from C23:

&num;define iter (times) for(typeof(times) i = 0; i < times; ++i)
Use of C23 typeof in macros

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# C# var: auto.

auto i = 3;
C23 type-inferring keyword

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!

Leave feedback! (via email)