21 January 2024 · 6 min
One of the established and most popular programming languages is the C programming language. It is relatively easy to learn, and highly practical.
Maybe surprisingly, the C programming language keeps evolving, slowly and carefully. If you have GCC 13 or LLVM (Clang) 16, you already have a compiler with some of the features from the latest standard (C23).
// Only include stdio.h if it exists #if __has_include (<stdio.h>) #include <stdio.h> #endif #include <stdlib.h> [[deprecated]] void f() {} [[nodiscard]] int g(int x) { return x + 1; } int main() { f(); // compile-time warning: 'f' is deprecated g(1); // compile-time warning auto x = 0b1111; typeof(x) y = 1'000'000; // type of y is the same as x printf("%d\n", x); // prints 15 printf("%d\n", y); // prints 1000000 constexpr int N = 10; // compile-time asserts using static_assert static_assert (N == 10); bool a[N]; // array of N booleans for (int i = 0; i < N; ++i) { a[i] = true; } printf("%d\n", a[0]); // prints 1 }
-
- The first part of the code contains some preprocessor directives, which are instructions for the compiler to process the source code before compiling it. The #if directive checks a condition at compile time and includes the following code only if the condition is true. The __has_include macro is a feature of C++17 adopted by C23 that checks if a header file exists and can be included. In this instance, it is not useful because we know that stdio.h is present, but in other instances, this can prove useful to determine what headers are available.
- The next part of the code defines two functions with attributes, which are annotations that provide additional information to the compiler about the behavior or usage of a function, variable, type, etc.
- The [[deprecated]] attribute is a feature of C++14 adopted by C23 that marks a function as obsolete and discourages its use. The compiler will issue a warning if the function is called or referenced.
- The [[nodiscard]] attribute is a feature of C++17 adopted by C23 that indicates that the return value of a function should not be ignored or discarded. The compiler will issue a warning if the function is called from a discarded-value expression.
In this case, the function f is deprecated and does nothing, while the function g returns the input value plus one and should not be ignored. The first two lines of the main function call the functions f and g and trigger the warnings.
- The third line of the main function declares a variable x with the auto keyword, which is a feature of C++11 that lets the compiler deduce the type of the variable from its initializer. In this case, the initializer is a binary literal, which is a feature of C++14 and adopted by C23 that allows writing integer constants in binary notation using the prefix 0b. The value of x is 0b1111, which is equivalent to 15 in decimal.
- The fourth line declares another variable y with the typeof operator that returns the type of an expression. In this case, the expression is x, so the type of y is the same as the type of x. The initializer of y is a digit separator, which is a feature of C++14 adopted by C23 that allows inserting single quotes between digits in numeric literals to improve readability. The value of y is 1’000’000, which is equivalent to 1000000 in decimal.
- The seventh line declares a constant variable N with the constexpr keyword, which is a feature of C++11 adopted by C23 that indicates that the value of the variable can be evaluated at compile time. The value of N is 10. Previously, one would often use a macro to define a compile-time constant (e.g., #define N 10).
- The eighth line uses the static_assert keyword, which is a syntax of C++11 adopted by C23 that performs a compile-time assertion check. The keyword takes a boolean expression and an optional string message as arguments. If the expression is false, the compiler will emit an error and stop the compilation, displaying the message. If the expression is true, the compiler will do nothing. In this case, the expression is N == 10, which is true, so the compilation continues.
- The ninth line declares an array of N booleans named a. An array is a collection of elements of the same type that are stored in contiguous memory locations. The size of the array must be a constant expression for standard C arrays (otherwise it becomes a variable-length array which may be less efficient), which is why N is declared with constexpr. We also use the keywords true and false which become standard keywords in C23.
There are many more features in C23, but it will take some time for compilers and system libraries to catch up.
My thoughts so far:
- The introduction of constexpr in C will probably help reduce the dependency on macros, which is a good idea generally. Macros work well in C, but when a bug is introduced, it can be difficult get meaningful error messages. It does not happen too often, but in large code bases, it can be a problem.
- I personally rarely use auto and typeof in other languages, so I don’t expect to use them very much in C. In some specific cases, it can greatly simply one’s code, however. It is likely going to help reduce the reliance on macros.
- The idea behind static_assert is great. You run a check that has no impact on the performance of the software, and may even help it. It is cheap and it can catch nasty bugs. It is not new to C, but adopting the C++ syntax is a good idea.
- The __has_include feature can simplify supporting diverse standard libraries and test for available libraries. For example, it becomes easy to check whether the standard library supports AVX-512. If a header is missing, you can fail the compilation with instructions (e.g., you need to install library X). It is generally a good idea for people who need to write portable code that others can rely upon.
- I did not include the introduction of char8_t in the language. I worked extensively with Unicode in C++ and I have not found good use cases for the char8_t type so far: char is always sufficient in my experience.
Daniel Lemire, "C23: a slightly better C," in Daniel Lemire's blog, January 21, 2024, https://lemire.me/blog/2024/01/21/c23-a-slightly-better-c/.
[BibTeX]
Typo, should be GCC 13 not GCC 23.
Defining constants is probably the most harmless and easiest to debug usage of macros in C, so I wouldn’t be that optimistic.
That said, pleasant surprises from C. It seems they finally picked up a bunch of easy quality-of-life improvements from C++.
Aren’t type safe, constexprs are.
Source?
Constexpr functions are coming to C in the future, WG14 just wants more implementation experience with it.
The introduction of consexpr in C will probably help reduce the
dependency on macros, which is a good idea generally.
Thanks for your indepth yet simple introduction to the new features of C. I love C language (not C++) but in these last years I am writing more code in other languages than C.
It is great to see that C continues to improve.
Any features without a c++ heritage?
NO
Yes.
#embedIt has more features but it’s not necessary better.
Looks like good features, beside of auto. auto is the new goto. There is nearby no place where auto makes any sense and only obfuscates which datatype is really used.
“The new
goto” sounds good, asgotocontinues to be useful even with modern C.a goto is useful to one programmer projects.
The Linux kernel, a thousand-programmer project, would like to have a word with you. Along with many other big open source projects.
I have 2 library with this same name of function
what I can to do in c23?
no namespace
You can prefix your API: mylibrary1_f and mylibrary2_f. It works.
Thus rendering it not backward compatible
How is backward compatibility relevant?
Name your functions more descriptively, namespaces just encourage bad naming practices.
Use
dlopen()anddlsym().C libraries should prefix all their externally visible symbols with something like a “namespace” (e.g. xyz_func1, xyz_func2, etc.). Any C lib that doesn’t do so is poorly written and probably not the kind of thing you’d want to use anyway.
Static assertions were added in C11 already. C23 just deprecates the old “_Static_assert” spelling and makes the message argument optional.
In the blog post:
> It is not new to C, but adopting the C++ syntax is a good idea.
I honestly read that bit to mean you were referring to the age-old C89-compatible static_assert, extern char checkit[2*!!(C)-1].
Some of that confusion is due to the fact that the “C++ syntax” static_assert was always the intended one, it’s just that you needed to include assert.h to get it (similarly for alignof and so on). And I fully expect you knew that, which makes this part all the more difficult to understand. Perhaps the problem was that including assert.h in library headers may not sound a good idea? (Though the standard explicitly makes it valid to include it multiple times with different states of NDEBUG.) I’m not sure.
The change is specifically to adopt the C++ syntax in C: WG14 N2265: Harmonizing static_assert with C++.
That’s not an opinion I am expressing, that’s what the people working on the C standard wrote.
I don’t like the double square bracket notation. This will make it annoying to embed bits of this C in markdown etc which already use that to mean hyperlinks.
Why aren’t these pragmas? That notation already exists for C and could be used for other compile-time checking hints.
Markdown doesn’t use [[…]] for anything, rather […][…] (see e.g. https://daringfireball.net/projects/markdown/syntax, https://spec.commonmark.org/0.30/, https://github.github.com/gfm/). It’s true some wikitext formatters do. I can’t imagine this being a problem in practice.
startic_assert (and regular assert) came to C in C11, not C23.
What are your thoughts on Operator Overloading Without Name Mangling?
https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3201.pdf
Regular assert has been around since ANSI C (see https://en.wikipedia.org/wiki/Assert.h)
From the blog post:
Do you expect C to add operator overloading?
What do you mean by expect? If we get the semantics right, I think C will accept it.
I’d be interested in seeing a use case for operator overloading that solves a problem.
Sized strings, math libraries, matrix math, SIMD.
So you are saying that the current semantics are wrong? What is wrong?
There is no current semantics for overloading operators in C…
My proposal is not to copy C++’s overloading feature, you should read the paper I linked.
It’s an interesting idea if it can be made to work. What I would most like to see is templates.
That’d be the death of C. Only idiot C programmers would accept that.
Seems like C23 is filling up with bloat.
constexpr is great at making your code less reusable, a design philosophy REJECTED by The original C designers but the code Nazis wanting to force others to program “the right way” (not too different than the people wanting to force others to speak “the right way”) have finally worn down the standards committee into submission…
I have never seen a usage of “auto” that i wouldn’t object to in a Google SWE C++ readability code review. So happy to see they are expanding the opportunity to make this mistake …
And so on and so forth …
Bloat? Not having decent compile-time language features is what adds actual bloat, i.e. the sort you can measure in machine code and execution time.
I’m curious to hear your opinion about things like
_Generic()and “stdatomic.h` . Or C11 in general.If anything, C23 is a very conservative step. There are still many more to take ot will genuinely improve C while staying true to purpose.
All that aside, here’s a tongue-in-cheek suggestion for a native template syntax:
#template<my_tmpl.h>
Recently I was thinking (in the context of C++, but still) that the
autokeyword is very useful if you use an IDE which supports replacing it with the explicit type. For long types, I sometimes now find myself writingautoonly to swap it a few instants later. With shortcut its much faster that writing out or sometime verifying the correct type through context.stdbool.h defines false/true in C99 (context; not a correction that it’s now a keyword).
“The size of the array must be a constant expression” — this (mostly) hasn’t been true since C99, which introduced variable length arrays (see https://en.wikipedia.org/wiki/Variable-length_array). VLA support became optional in C11.
I think the point OP is trying to make is that
constexprguarantees the array is not a VLA.IIRC, there is nullptr (simal to NULL) too. Which is great.
I love the __has_include feature, but not as just a guard to include: it can make portability easier (no need for loads of compilee -D options). It could be useless however if Visual Studio takes 10 years to support it.
I don’t see how the auto keyword can actually work in real code: see the subsequent printf in the example that assumes that this is an int (“%d”). So the auto type may not always work and cause hard-to-find bugs. It is bad when one cannot write a small usage example without hitting a basic consistency problem..
The typeof keyword can be useful even if auto is not used: you make the type decision only once and it is easier to change it later. However it can make code convoluted if that dependency propagates through several modules. Using typedef is still the gold standard, IMHO.
You can use true and false, but you cannot print them?? (It prints as 1 in the example.)
Many of these changes look like half backed designs.
I am pretty sure __has_include is supported today in Visual Studio.
Printf’s quirks aren’t even tangential to auto typing. If you want to spice up your format strings you can make use of _Generic() to some extent.
Also, auto is basically just a shorter typeof. Many of the benefits you see in typeof apply to auto just as well.
#define shitty_auto(x, ...) \
typeof(__VA_ARGS__) x = __VA_ARGS__
I do a lot of embedded development, but where available, I typically choose C++, primarily due to things like template and constexpr functions and such. I use metaprogramming to efficiently deal with things like graphics code, while maintaining flexibility – something you can’t really do in many cases in C without code generation during the build step – the preprocessor can only get you so far in that regard.
I am thrilled to see constexpr gaining ground in C. If I could start achieving compile time expressiveness that I can in C++ I’d have little reason to use C++. As it is I already forgo the STL.
“You can use true and false, but you cannot print them?? (It prints as 1 in the example.)”
The handling of enum’s is still underdeveloped in the C-Space.
The compiler knows the values and the keywords. Why is there no min- and max-value? Why no enum to const char *? Of course printf would not work, but (const char *)a[i] could, since a cast from enum to const char * does not have to go through int.
What do you mean with casting enum to const char pointer?
The Compiler could calculate the Text value of the enum. In case of boolean “true” or “false”. Just, what is declared for this value or NULL, if the value is not declared in the enum. This generates a Lot of Strings, If used often, but Most compilers can handle This.
I thought C is not evolving anymore/has been final. So C is not obsolete, right?