Advanced Metaprogramming in C: A select statement
250bpm.com
250bpm.com
In my experience, a proclivity to programming serious low-level code in C very much comes with an intense defensive reaction to things like "metaprogramming using the C precompiler". At least I still vividly the remember my co-workers' reaction to the C code I wrote as an intern, when I tried to be clever.
Then came libdill, which created the whole "structured concurrency" movement and brought new concepts to light that are now being replicated in python, kotlin, java...
I mean.. a lot of code that's written in C isn't particularly "low level" in the sense that it's optimizing for runtime or memory in any kind of outrageous way. A lot of C is very normal/straight forward code which can benefit greatly from metaprogramming due to the lack of language-level niceties.
> I still vividly the remember my co-workers' reaction to the C code I wrote as an intern, when I tried to be clever.
That's understandable. I'm highly skeptical of clever code anyone writes, including myself.
I guess what I'm trying to say here is that metaprogramming and being clever aren't mutually intertwined ideas.
The best solution I have found, disregarding of language, is to use the same language to write a program that generates the code you need.
Then you can compile, and debug them separately using the same workflow, and each program can be clear and simple to read. Its not sexy or clever, but it works and anyone can understand what is going on. Almost always that turns out to be the most important thing.
Sounds like a pitch for lisp; I haven't really used any lisp variant much, but I always hear about people saying they basically write a program to generate their program rather than actually just solving the problem directly.
[0]: https://doc.rust-lang.org/reference/procedural-macros.html [1]: https://github.com/dtolnay/cargo-expand
Also, unrelated, but I miss watching your streams on Twitch, assuming you're the same quelsolaar :)
https://www.sav.com/auctions/details/1159199/libmill.org
Insert some joke about the dangers of overly abusing the C precompiler…
- portability
- generate strings from identifiers
- Insert file and line of the call location (or other context) automatically when calling a function.
- X-macros
- Scopes with exit statement given at beginning, hacked using "for" statement and a condition that evaluates to true only the first time. A little bit brittle when the exit statement must not be skipped (goto, return).
I'm sure there are more, but I'm too tired to think harder... Here is an example of the last category:
#define TIMED_SECTION_(name, t) FOR_SCOPE(Timer t = start_timer(); print_passed_time(name, t))
#define TIMED_SECTION(name) TIMED_SECTION_(name, UNIQUE_NAME())
Not giving the FOR_SCOPE() and UNIQUE_NAME() macros here for brevity. They add another 3-5 reusable lines. Use like this: TIMED_SECTION("foo the bar")
{
/* Do stuff, time spent in this scope will be measured and printed on scope exit. */
}
Other more advanced languages can do some of the same things, sometimes more robustly, or not at all (X-macros is probably hard to replace for languages that are not LISP). But it typically comes at a cost in complexity, hard to use type systems, complicated semantics with edge cases... And they only allow you to do what is already built into the language!C just refuses to improve, even though there are a huge space for those.
Not saying that other approaches are bad, just that C has some qualities that are underappreciated, or simply not understood by many. In case of the preprocessor, while it's certainly flawed, it's good that it's a mechanism that is completely orthogonal to the language. Which means it doesn't interact with the normal syntax and semantics of the language, isn't a complexity multiplier. There is a single, well-chosen interaction point: the lexical syntax (token stream).
This makes it hard or impossible to implement certain user-defined extensions in a robust way, but also allows you to do many important tasks of factoring out syntactic boilerplate just fine. And you can do some extremely practical hacks with it that other systems just don't let you do at all. In any case, the cost of using it is very "self-contained".
I don’t know, doing what equals to find and replacing string inside a Programming language (with a bit exaggeration on my part) seems like a terrible terrible idea, even just for the very trivial reason of outputting paired number of brackets. It is also terrible from a readability and maintainability point of view with quite a few insane hacks of hacks done to implement basic functionality in other languages (there is an infamous macro in the linux kernel that is so cleverly hacky that it is on another level).
And all that for nothing, as the language is incapable of expressing even such a basic thing as a generic array/vector-like data structure without a huge indirection, let alone more complex things.
Minimalism is a nice goal, but without abstractibility/expressivity it is useless.
Probably that says more about the particular programmer than it says about macros. Also on a Linux kernel scale some tradeoffs are different.
> And all that for nothing, as the language is incapable of expressing even such a basic thing as a generic array/vector-like data structure
Something you often do not do when programming in C. You do not want "vector", reallocating by a constant factor without second thoughts, for "serious" systems programming. Same goes for much of the other stuff that is in STL for example.
And you can in fact design macros in a few lines that allow you to do something like
DECLARE_CHUNKLIST(My_Foo_Type) foo_chunklist;
CHUNKLIST_PUSH(&foo_chunklist) = make_foo();
I.e. In fact no terrible noise involved. The hack employed here is #define DECLARE_CHUNKLIST(type) union { type *_type; struct { /* chunklist implementation ... */ } _impl; }
which allows the list to carry its own type to support operations like CHUNKLIST_PUSH. For good ergonomics this relies on typeof().But again, there is a lot of "serious" programming that gets by just fine without any of such hacks. Sometimes you're programming an event handling system where you mostle use fixed-size but dynamically-allocated buffers and push those around. There is very little generic code used here, the functions you write are dealing with concrete flat buffers mostly.
~ Voltaire
Is it just me who thinks this was by far the clearest, cleanest formulation? Everything following it seemed to sacrifice clarity for the sake of cleverness.
Also I really dislike macros that try to disguise themselves as "not macros". Capitalise the crap out if it, make the fact that this is a macro stand out, man!
Initializer lists aren't a thing in C, so I assume you're referring to array initialization : https://en.cppreference.com/w/c/language/array_initializatio...
And with that said, I fail to see how using an initializer would have any effect on the end result.. unless of course you only read the first 150 words. Then your comment, while unnecessary, would make sense.
struct Foo {
int x, y;
};
struct Foo foo = {1, 2};
// ^ this is an initializer list.
They also include designators, like: struct Foo foo = {.x=1, .y=2};
The straw man struct initialization at the beginning of the article would be greatly improved with such a construct. Once you are no longer messing with macros, the rest of the article becomes a step too far.I assume the article was for c89, as c89 did not allow non-constant initializers. Any modern C compiler should compile c99 or newer code though.
> Can we somehow not require the user to specify the size of the pollset in advance?
The answer for gnu99 is yes.
// will barf if fed a pointer
#define sizeof_array(arr) \
(sizeof(arr) / sizeof((arr)[0]) \
+ sizeof(typeof(int[1 - 2 * \
!!__builtin_types_compatible_p(typeof(arr), \
typeof(&arr[0]))])) * 0)
typedef struct
{
int x, y;
} Foo;
Foo foo[] =
{
{ .x = 10, .y = 20},
{ .x = 11, .y = 23}
};
foo_cnt = sizeof_array(foo); // foo_cnt == 2
One of the reasons why I think the C standards committee needs to be fired is because sizeof_array should have been made part of the language 20 years ago.