Preprocessor magic: Designated Initializers in C Function Calling

A post explaining the power and use of designated initializers in the C99 standard. Examples along with source code are provided to show what is possible to be accomplished using designated initializers.

Inspired by the discussion in the comments of the previous post on using default argument in C using the preprocessor, I am going to write about using the C99 designated initializers feature on functions rather than just simply in an initializer list of a structure.

But first of all let’s see what a designated initializer list actually is. It is a feature introduced in the 1999 revision of the C standard. It currently is not a part of the C++ standard so you can’t use it in C++, but only in C. Of course in C++ you can just make a C file, include it in your project and do anything you want with designated initializer lists in there. Then in the point where you include the C-specific code just do :

#ifdef __cplusplus
extern "C"
{///opening bracket for calling from C++
#endif
 
... C code ...
 
#ifdef __cplusplus
extern "C"
}///closing bracket for calling from C++

So what is a designated initializer list? It is something that enables you to give values to specific (designated) parameters of a struct using an initializer list. Refer to the following code for an example.

// a structure with some parameters
typedef struct a_struct
{
    int param1;
    int param2;
    int param3;
}a_struct;
 
// using a normal initializer list
a_struct a1 = {10,20,30};
// using a designated initializer list
a_struct a2 = {.param3 = 30, .param1 = 10};

As we can see, in contrast to the normal initializer list, you can use the syntax of <.variableName = value> inside an initializer list to provide values of only specific parameters of a struct in any order you desire.To see more information about designated initializer lists and also the limitations of their use read the C99 standard, Section 6.7.8, page 125.

So having established what we are dealing with let’s define a structure in a header file (say foo_header.h) that will be the main subject of our experiment which we will call foo. The first 4 members are going to be used during construction and the others are in there just to show that foo can have more member parameters than the first four. Then just like in the previous post we shall define another structure which will only be representing the arguments given to initializing foo and we shall name that structure as foo_arguments. Then we declare three functions which are:

  • i_foo_init():To normally initialize a foo struct
  • i_foo_init_arg():To initialize a foo struct using the compound literals method introduced in the previous post
  • foo_init(): This is the macro that we will be using to actually initialize foo with designated initializers
// Defining the default values of the variables
#define V1_DEFAULT  2006
#define V2_DEFAULT  0.659
#define V3_DEFAULT  'A'
#define V4_DEFAULT  "I am a default value"
 
 typedef struct foo
{
    //These are the foo members that are typically initialized during construction
    int var1;
    float var2;
    char var3;
    char* var4;
 
    //these are members used by foo and not initialized during construction
    int otherData1;
    char* otherData2;
}foo;
 
//We create a struct to hold the initializing parameters of foo
typedef struct foo_arguments
{
    int var1;
    float var2;
    char var3;
    char* var4;
}foo_arguments;
 
 
// The normal foo initializer
foo* i_foo_init(int v1,float v2,char v3,const char* v4);
 
// The variable argument initializing function for foo
foo* i_foo_init_arg(foo_arguments args);
 
// This is the function that will be initializing foo
#define foo_init(...) i_foo_init_arg( (foo_arguments) {__VA_ARGS__} )

Now in a corresponding source file (foo_source.c) we will input the definition of the functions.

#include "foo_header.h"
 
// The normal foo initializer
foo* i_foo_init(int v1,float v2,char v3,const char* v4)
{
    foo* ret = malloc(sizeof(foo));
 
    ret->var1 = v1;
    ret->var2 = v2;
    ret->var3 = v3;
    ret->var4 = v4;
 
    return ret;
}
 
// The variable argument initializing function for foo
foo* i_foo_init_arg(foo_arguments args)
{
    int v1;
    float v2;
    char v3;
    char* v4;
 
    v1 = (args.var1 !=0)? args.var1 : V1_DEFAULT;
    v2 = (args.var2 !=0)? args.var2 : V2_DEFAULT;
    v3 = (args.var3 !=0)? args.var3 : V3_DEFAULT;
    v4 = (args.var4 !=0)? args.var4 : V4_DEFAULT;
 
    return i_foo_init(v1,v2,v3);
}

Now what does the above do? If you read my previous post you would know, but let me re-iterate for those who did not. i_foo_init() is your standard initializing function. Takes in the values of the parameters and returns the initialized structure. On the other hand i_foo_init_arg() takes in a foo argument list and reads its members in order to initialize a foo function. If a member parameter is set, then its value is given to the initialized foo, if not then the default value is given to foo. But why go through this extra layer of initialization? What do we gain?

Well the answer lies in the definition of the actual initialization macro (copied below for your convenience). Notice that the variable arguments are put inside an initializer list but with the “cast” in front we obtain a compound literal statement. This means that the arguments given to foo_init() are turned into a foo_arguments structure and then passed to our i_foo_init_arg() function.

#define foo_init(...) i_foo_init_arg( (foo_arguments) {__VA_ARGS__} )

And this is where the magic happens. Let’s say that we are inside the main function now. We can now use designated initializers with foo_init() even though it is a function. Why? Because of the extra initialization level at i_foo_init_arg where we are turning the arguments into an initializer list, hence turning them into a structure which itself can accept designated initializers. For examples look below

int main()
{
    //initializing only the last 2 parameters
    foo* f1 = foo_init( .var3 = 'c' , .var4 = "I am given by a designated initializer list");
    //no value given, all values are default
    foo* f2 = foo_init();
    //initializing all the parameters in a screwed up order
    foo* f3 = foo_init(.var4 = "Hello", .var2 = 2.71828, .var1 = 100, .var3 = 'Z');
    //initializing only one argument and the rest are default
    foo* f4 = foo_init(.var2 = 3.14159);
 
    //--the big limitation of this method--  (Gets initialized with default values since 0 gets interpreted as no value given
    foo* f5 = foo_init(.var1 = 0, .var2 =0, .var3 = 0, .var4 = 0);
 
    return 0;
}

We can see from the examples above that we can initialize any argument we want and in any order. That’s pretty awesome and can come in handy in many situations in an actual program. On the downside as also stated in the previous preprocessor post we can’t give 0 as the value of an argument since this is the way we detect if no value has been given. Look at the initialization of f5 for example. One would expect to see it full of 0s but instead, foo f5 is initialized with all the default values. Why? Because when the initialization gets inside i_foo_init_arg() it detects the 0s and substitutes them with the default value. So this method is pretty nifty as long as you realize the danger of using 0s for initialization and avoid them.

So in conclusion in this post we saw a way to utilize designated initializer lists in function parameters instead of just the usual way of initializing structures. It is a pretty nice method in my opinion and can offer really concrete advantages to the programmer by making his code a lot more readable and easy to write. At the same time there is the problem of passing 0 as a value of an argument which has to be kept in mind if one uses this method. If you have any comments, found some mistakes in my code above or want to discuss the method presented please leave a comment. All feedback is appreciated and helps in improving. Also if you liked this post I would suggest you follow me on Twitter for more updates, comments and rants on programming and stuff in general. Thanks for reading!

1 thought on “Preprocessor magic: Designated Initializers in C Function Calling”

  1. may be this is no more actual but anyway.
    default values can be emulated by this constuction:
    (foo_arguments){.var1 = DEFAULT1, .var2 = DEFAULT2, .var3 = DEFAULT3, .var4 = DEFAULT4, __VA_ARGS__}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.