Roguelike Devlog

C Tips: sizeof

This tip is so small it's embarrassing to share but I personally enjoy learning about tiny quirks like this.

There are two distinct applications of the `sizeof` operator:

  sizeof <value>
  sizeof(<type>)

Since the syntax for the latter case is equivalent to the former with extra parenthesis, it's common to always use the latter syntax in code.

I'm certain this is also how the common C beginner misconception that `sizeof` is a function and not an operator is perpetuated.

It's a tiny thing but I avoid the interchangeable syntax. It makes some edge cases less brittle and keeps me cognizant of what I'm sizing.

  struct foo *f = malloc(sizeof(struct foo)); /* not great */
  struct foo *f = malloc(sizeof(*f));         /* better */
  struct foo *f = malloc(sizeof *f);          /* preferable */

Since parenthesis are required when `sizeof` is applied to a type we know immediately the final example must refer to data we are using and if we change the type in the LHS of the statement the RHS is still correct.

Or put as a rule:

get your sizes from your data not your types

C Tips: Generic Selection

C11 introduced generic selection expressions with the _Generic keyword. The general idea being the compiler can select an expression from a list of expressions to evaluate at compile time based on the type of it's first provided expression (commonly a variable).

An obvious use for this is polymorphic macros. For example if we wanted to make a generic DESCRIPTION() macro that gives us a string we can use for a given value:

const char *player_name(struct player *player);
const char *item_description(struct item *item);

#define DESCRIPTION(x) _Generic((x), \
    struct player *: player_name,
      struct item *: item_description)(x)

/* ... */

printf("description: %s\n", DESCRIPTION(thing)); /* we don't need to care what `thing` is now */

Another great use is the prevention of implicit integer conversions. Integer narrowing/truncation in particular which can result in information loss. While there are compiler flags such as -Wconversion and tools like clang-tidy that can catch these I find that I often do a naive cast to fix the warning and not the correct thing.

I ran into this recently with the compiler builtin ctz (count zeros).

int __builtin_ctz (unsigned int x)

If we provide a larger integer type such as an unsigned long long we'd get a truncation. This could be very bad as the truncated bits could contain info we need! Of course the compiler folks knew this so they provide sized variants:

int __builtin_ctzl (unsigned long)
int __builtin_ctzll (unsigned long long)

But it may not be apparent to a developer modifying this code that these variants exist. There's a decent chance they'd write a cast or even add extra code to check and make sure it's safe. Instead of assuming the developer has that knowledge or would take the time to look it up, we can make a macro that picks the correct variant for us:

#define CTZ(x) _Generic((x), \
        unsigned int: __builtin_ctz, \
        unsigned long: __builtin_ctzl, \
        unsigned long long: __builtin_ctzll \
    )(x)

Another handy use for this pattern is as a type assertion to prevent accidental conversions for just one case:

#define CTZ(x) _Generic((x), unsigned int: __builtin_ctz)(x)

Now if we provide a different type the compiler will complain even when conversion warnings are turned off. Nice!

Tall Level Experiment

Game

Trying more level experiments to see what sort of design/perf issues arise.

Screenshot of a tall level experiment.

About 1/6 of the total height is visible in the screenshot, the shading obscures the rest as it becomes completely black.

Codex

To cut down on extra curricular yak-shaving the wiki/devlog was switched from rust to ruby.