Contents

C11 and C23 feature highlights

This is a short overview of things added along with c11 and c23 which I find useful or interesting.

auto keyword… yet again

auto has been repurposed in C23. Originally, it defined a storage duration for local variables (similarly as static) now, comparably as in C++, it can be used for type inference purposes.

1
2
3
4
5
6
7
void foo() {
    // auto is implied
    int i = 123;

    // same thing, auto keyword is redundant
    auto int j = 123;
}

In its original purpose, variables marked with auto have their storage automatically allocated and deallocated on scope entry and exit. It was implied for all local, stack variables. No one really used auto explicitly because it was just unnecessary now, it has an extra meaning.

It’s useful in most expected contexts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void foo() {
    auto i = 123;
    auto f = 3.14f;

    for (auto i = 0u; i < 10u; ++i) {
        ...
    }

    auto res = function_call();
}

Unfortunately, since C has no way to support function overloads and generic functions, it can’t be applied to function arguments or return values i.e.:

1
2
3
4
5
6
7
8
9
// won't compile
auto foo() {
    return 123;
}

// won't work either
void bar(auto a, auto b) {
    // ...
}

More details in the proposal: N3007.

Attribute specifiers

Another addition similar to what we’ve got in C++ are attribute specifiers. C23 introduces the following:

  • [[deprecated]]
  • [[fallthrough]]
  • [[nodiscard]]
  • [[maybe_unused]]
  • [[noreturn]]
  • [[unsequenced]]
  • [[reproducible]]

Here’s some usage examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[[deprecated("This API has been replaced by 'bar'")]]
void foo() {
    // ...
}

[[nodiscard]]
int importantResult() {
    // ...
}

[[noreturn]] void runForever() {
    while (true) {
        [[maybe_unused]] auto x = importantResult();
        switch(x) {
            case 0:
                // ...
                [[fallthrough]]

            case 1:
                // ...
                break;
        } // switch
    }
}

_Generic

Contrary to what one might believe on first glance, _Generic does not provide similar functionality as generics in C++ or any other language. This is not a mechanism to declare generic types, variables, functions or object templates. In its essence, it’s just an expression selector. It’s composed of a controlling expression and a list of generic associations to implement a so called Generic selection. It allows to choose an expression, in compile time, from the list of expressions in generic associations list based on the type of the controlling expression. Here’s a simple example:

1
2
3
4
5
6
void foo() {
    auto i = _Generic(1u + 2u,
        unsigned: 100,
        double: 123.0f,
        default: 200);
}

The 1u + 2u is a controlling expression. Its resulting type will be unsigned. So, for this case, the _Generic will choose, at compile time, the expression 100 (associated with unsigned type) as an expression used in an assignment to variable i.

Of course, this example is not very useful since it’s using static data.

_Generic seems to work best to implement function overloading, just like in C++. Consider the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <math.h>

#define SIN(X)              \
    _Generic((X),           \
        double: sin,        \
        long double: sinl,  \
        float: sinf,        \
        default: sin)(X)


int main() {
    // The expression for default type will be used - sin.
    // `a` will be of type `double`
    [[maybe_unused]] auto a = SIN(3u);

    // The controlling expression is of 
    // type float - sinf will be selected.
    // `b` will be of type float.
    [[maybe_unused]] auto b = SIN(3.14f);

    // The controlling expression is of type `double`.
    // `c` will be of type `double`
    [[maybe_unused]] auto c = SIN(3.14);

    // The controlling expression if of type `long double`
    // `d` will be of type `long double`
    [[maybe_unused]] auto d = SIN(3.14L);
}

SIN might be thought of as a function with three overloads, selected at compile time, for types double, long double and float. These overloads are implemented with sin, sinl and sinf. _Generic allows them to share a common interface.

_Generic was introduced in C11, more can be found on cppreference.

nullptr and nullptr_t

Along with C23 nullptr_t type and its predefined constant nullptr has been introduced to avoid implicit conversion of integer types from/to NULL. It’s a small change that was introduced in C++ along with c++11.

The problem is well understood by now. It’s a nice to have, making C at least a little bit safer. More details available on cppreference.

#embed - binary inclusion

This is a great feature allowing for inclusion of data as binary blobs into the source code. #embed has been accepted in C23. There’s a similar proposal for C++ which is still a work in progress. At the time of writing, only clang19 actually implements it. Still though, it’s something I’m really happy with.

Usually, as a workaround you’d generated a header file with a tool like xxd:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ xxd -i mydata.txt
unsigned char mydata_txt[] = {
  0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x76, 0x65,
  0x72, 0x79, 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74,
  0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68,
  0x61, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x63,
  0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x62,
  0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x0a
};
unsigned int mydata_txt_len = 72;

With #embed this is now supported directly by the compiler:

1
2
3
4
const char mydata[] = {
#embed "mydata.txt" if_empty('M', 'i', 's', 's', 'i', 'n', 'g', '\n')
,'\0' // null terminator
};

I’ve tested that myself with clang19 and it worked like a charm. Now, writing quines has become trivial. Looking forward for the same feature in C++.

Static asserts

_Static_assert has become a keyword on its own: static_assert. It’s similar in nature to its C++ protoplast but quite limited by C itself. Again, since there’s no function templates, object templates, constexpr functions and all the cool stuff we have in C++, all it can do is assert on integer constant expressions which isn’t really that useful.

It can be combined with _Generic to perform some compile time checks on its result but that’s the best I can think of i.e.:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <limits.h>

#define NUMERIC_MAX(ARG) \
    _Generic((ARG), int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX)

#define NUMERIC_MIN(ARG) \
    _Generic((ARG), int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN)

void foo() {
    static_assert(NUMERIC_MAX(123) == INT_MAX);
}

Conclusion

All these changes are definitely a step in the right direction. It’s definitely better late then never regardless of C’s diminishing influence and popularity.

One concerning factor is that the divergence between C and C++ seems to be widening in some regards. This is not necessarily a good thing and might introduce unneeded confusion considering the fact that majority of the features adapted to C are already well established in C++. For such things, I’d consider a direct, compatible port (as much as technically possible) to be a much better approach.