1

I've been looking through a program called hickit, and at one point (count.c, function starts at line 105), and they call a macros function (kavl_insert) from the Klib library as follows:

static void hk_count_nei2_core(int32_t n_pairs, struct cnt_nei2_aux *a, int r1, int r2) { struct cnt_nei2_aux *root = 0; int32_t i, j, left; unsigned cl; left = 0; kavl_insert(nei2, &root, &a[0], 0); ... 

Looking at the Klib library (more specifically, in kavl.h), this function (I think) is defined as follows:

#define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \ __scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \ 

Later on in the kavl.h file there is this standalone line (line 322):

#define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt) 

I don't have much technical knowledge with C (just learned parts as they were relevant), and I'm wondering how this works. The casing is different, and there is the "__" precursor in the #define line. How does this work?

6
  • It doesn't "work". As you noted, the casing and __ are different. There must be an actual kavl_insert defined somewhere. Commented Aug 17, 2020 at 20:17
  • I took a look and found something sort of like that. Is there a connection between the __KAVL_INSERT and kavl_insert? Commented Aug 17, 2020 at 20:20
  • Not in the code that you've shown. Maybe kavl_insert_nei2 (that is, kavl_insert_##suf with suf = nei2) calls it, maybe not. Commented Aug 17, 2020 at 20:20
  • What does the "__scope __type" mean here? I was thinking if there was a connection it would be there, but it seems not Commented Aug 17, 2020 at 20:22
  • __scope and __type are the parameters of the macro. Judging by the name, __type is the return type (here * is always added to the type), but to be certain you have to see how the macro is called. Commented Aug 17, 2020 at 20:25

1 Answer 1

1

The first __KAVL_INSERT macro is used to declare functions which all start with the same prefix (kavl_insert_) and end with the specified suffix (parameter suf).

So, when you see this:

__KAVL_INSERT(foo, static, int, null, null) 

preprocessor will replace it with a function with the appropriate name, scope, and parameter types:

static int *kavl_insert_foo(int **root_, int *x, unsigned *cnt_) { \ /* actual function body ... */ \ /* with lots of trailing backshashes ... */ \ /* because it's the only way to create ... */ \ /* a multiline macro in C */ \ } 

The lowercase kavl_insert macro, on the other hand:

kavl_insert(foo, &something, &whatever, 0); 

simply expands to the actual function call, i.e. it's equivalent to calling the function defined above:

kavl_insert_foo(&something, &whatever, 0); 

The idea behind this kind of macros is usually to create a generic type-safe data structure in C, using the preprocessor, like the klib library of various generic data structures.

Sign up to request clarification or add additional context in comments.

11 Comments

Thank you for your reply! To clarify, how does the preprocessor know to replace __KAVL_INSERT with a function with the appropriate name, scope, and parameter types? Is it because of the line following, __scope __type *kavl_insert__##suf(...)? Also, what you wrote, static int *kavl_insert_foo(int **root_, ...), is it a more generalized form of what is here, __scope __type *kavl_insert_##suf(__type **root_,...)? And finally, to clarify, is kavl_insert(foo...) what is called in the code, and it iself expands to the above-defined _KAVL_INSERT static int *kavl_insert_foo(...)?
@Chris: double hash sign (##) tells the preprocessor to concatenate two strings, so kavl_insert_##suf means "append the suf parameter passed to the macro to the kavl_insert_ string. If you use the -E parameter with gcc (you can check this with the godbolt online compiler), you can see how the preprocessed file looks like before the compilation stage. Left pane in the linked webpage shows the source code, right pane shows the final result for gcc 8.2 with -E.
The reason klib is doing all this might become apparent if you decide to write your own generic data structure. For example, a vector-like data structure is very usable in C (a struct which contains a pointer to an array, its capacity and its current length, and automatically increases the capacity as needed). If you wanted to pass around a list of int vectors, or char vectors, or a vector of any custom structs, you would have to copy all the functions over and over again (or use char*/void* and then cast all around the code, which makes the code prone to runtime mistakes).
Is the kavl_insert(foo, ...) connected to __KAVL_INSERT(foo,...), and if so, how? Sorry if I'm missing something. It looks to me that KAVL_INSERT(foo...) declaring functions that start with kavl_insert_ would exclude `kavl_insert', as it lacks that last underscore.
If you want to call x_b, you need to declare it somewhere using _X(b), yes. If you need the x_type function for a specific type, you want to emit its entire source code through the preprocessor using the _X macro. I believe the best way to fully understand the concept is to try designing something like a dynamic array yourself. Go through this quick example and, once you understand what's going on, think of how you would reuse this code so that you can have a list of float or a list of struct something, like this.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.