cpar - Variable function parameters for C

Table of Contents

1. About
Bug Reports
2. Using cpar
Simple example
Creating a parameter table
Retrieving parameters
Modifying parameter tables
Merge Modes
Merge using CPAR_MERGE
Merge using CPAR_ADD_MERGE
Keep and Remove
The "seen" flag
The "default" flag
Flag Rules
Command-line processing
Empty and NULL parameter tables
The "real" type
The "string" type
Example code
3. Advanced Topics
Custom pointer types
Store by value

Chapter 1. About


The C language requires that you predefine the types and positions of all the parameters to every function. There is also the C variable parameters handling mechanism, although this is risky for several reasons. There are many cases when this is inconvenient:

  • You want to allow many parameters to handle special cases, but not have to pass them the rest of the time.

  • You want to add new features that require new parameters to a function, but don't want to break legacy code.

  • You want to rearrage the order of parameters for clarity, but don't want to break legacy code.

  • You want parameters to be named for clarity of your code.

All these problems are solved by this package.


The cpar library provides the following features:

  • Create a hash table containing named function parameters

  • Add named parameters to a parameter hash table

  • Merge or transfer parameter from one table to another

  • Load program command-line arguments into a hash table

  • Retrieve values from a table

  • Create default values for parameters

  • Modify the hash table in several ways

  • Pass void pointers while doing runtime type checking on them

  • List parameters and their values from a table


cpar depends on glib version 2.0 or newer.

Bug Reports

Send bug reports to

Chapter 2. Using cpar

Simple example

The following C program should make the basic principles clear:

void subroutine(int k, cpar_table *vp)
real *k2;
int i;
char *string;

string = CPAR_GET_STRING(vp, "key can be any string");
i = CPAR_GET_INT(vp, "key 1");
k2 = CPAR_GET_REAL_POINTER(vp, "key 0");
*k2 = 1.2345 * i * k;

void main(void)
real value = 0.0;
cpar_table *vp;

        CPAR_ADD_REAL_POINTER, "key 0", &value),
        CPAR_ADD_INT("key 1", 15000),
        CPAR_ADD_STRING("key can be any string", "contents");
// value now == 1.2345 * 15000 * 10

CPAR_CREATE_PARAMETERS() creates a parameter table, and three parameters are inserted. This table is passed to the subroutine, which can access the values of the parameters. The parameter table is freed by the main program using CPAR_FREE_PARAMETERS().

Types are checked each time a parameter is retrieved from the table. Type mismatch is a fatal error (this can be changed by setting CPAR_TYPE_MISMATCH_IS_FATAL to false in cpar_internal.h).

Creating a parameter table

cpar_table *vp;
vp = CPAR_CREATE_PARAMETERS(<parameters>)

where <parameters> is a list of the following functions:

char *key;

CPAR_ADD_BOOL(key, value)
CPAR_ADD_INT(key, value) 
CPAR_ADD_DOUBLE(key, value) 
CPAR_ADD_REAL(key, value)
CPAR_ADD_STRING(key, value)
CPAR_ADD_GENERIC(key, cpar_type, value)

CPAR_ADD_GPOINTER(key, gpointer_type, value)
CPAR_ADD_SV_GPOINTER(key, gpointer_type, value, size)
CPAR_ADD_CV_GPOINTER(key, gpointer_type, value, allocate_func, 
                     copy_func, free_func)


CPAR_ADD_TRANSFER(new_key, old_vp, old_key)
CPAR_ADD_TRANSFER_OVERWRITE(new_key, old_vp, old_key)
CPAR_ADD_TRANSFER_NEW(new_key, old_vp, old_key)
CPAR_ADD_TRANSFER_UPDATE(new_key, old_vp, old_key)

Most of the basic types are self explanitory. Note that one of the basic types is a pointer to a cpar table. This means you cast nest cpar tables inside each other.

CPAR_ADD_GENERIC() is a little different than the other basic types, because the type is a variable. The advantage of this is more flexibility, but disadvantage is that type checking can't be performed on the value of this function. Use the type-specific versions unless you need to change the type at runtime.

The gpointer type is described in detail in section the section called “Custom pointer types”

Merging cpar tables is described in detail in section the section called “Modifying parameter tables”

Transfering parameters is described in detail in section the section called “Transfer”

Retrieving parameters

There are two families of macro functions for retrieving parameter values.


The "assign" family takes a pointer and assigns the value of the parameter there. They return true if the parameter key was found and assigned to your pointer, false if not.

cpar_table *vp;
char *key;

CPAR_ASSIGN_BOOL(vp, key, pointer)
CPAR_ASSIGN_INT(vp, key, pointer)
CPAR_ASSIGN_UINT(vp, key, pointer)
CPAR_ASSIGN_DOUBLE(vp, key, pointer)
CPAR_ASSIGN_REAL(vp, key, pointer)
CPAR_ASSIGN_STRING(vp, key, pointer)
CPAR_ASSIGN_BOOL_POINTER(vp, key, pointer)
CPAR_ASSIGN_CHAR_POINTER(vp, key, pointer)
CPAR_ASSIGN_INT_POINTER(vp, key, pointer)
CPAR_ASSIGN_UINT_POINTER(vp, key, pointer)
CPAR_ASSIGN_REAL_POINTER(vp, key, pointer)
CPAR_ASSIGN_CPAR_POINTER(vp, key, pointer)
CPAR_ASSIGN_GPOINTER(vp, key, gpointer_type, pointer)


The "get" family return the value of the parameter. The program aborts with failure if vp doesn't exist, or if key doesn't exist in vp.

cpar_table *vp;
char *key;

bool         CPAR_GET_BOOL(vp, key)
int          CPAR_GET_INT(vp, key)
double       CPAR_GET_DOUBLE(vp, key)
real         CPAR_GET_REAL(vp, key)
char *       CPAR_GET_STRING(vp, key)
bool *       CPAR_GET_BOOL_POINTER(vp, key)
char *       CPAR_GET_CHAR_POINTER(vp, key)
int *        CPAR_GET_INT_POINTER(vp, key)
double *     CPAR_GET_DOUBLE_POINTER(vp, key)
real *       CPAR_GET_REAL_POINTER(vp, key)
cpar_table * CPAR_GET_CPAR_POINTER(vp, key)
gpointer     CPAR_GET_GPOINTER(vp, key, gpointer_type)
gpointer     CPAR_GET_RAW(vp, key)
int          CPAR_GET_TYPE(vp, key)

CPAR_GET_RAW() returns a gpointer to whatever is in the hash table for the key given. It is different from CPAR_GET_GPOINTER() because it doesn't check that the type of the pointer matches the type given when the parameter was inserted.

CPAR_GET_TYPE() returns the type number used by cpar for the key given.

Modifying parameter tables

There are many cases where you will want to change a parameter table before passing it on to to a deeper subroutine. These are the macro functions to modify parameter tables.

Merge Modes

Merging parameters into a cpar table has several possibilities. Do you want to overwrite parameters if they are already there? Do you want to only update existing parameters with new values, but not add new parameters? The answers to these questions determine the merge mode, which is used several places in the cpar library.

The merge modes are as follows:


Abort with error if a key being merged into a table already exists.


Always merge a new parameter.


Only merge a parameter if that key doesn't already exist in the table.


Only merge a parameter if that key already exists in the table.

The default merge mode is NO_OVERWRITE.


Add parameters to a cpar table or update parameters already there.

CPAR_UPDATE(vp, <parameters>)
CPAR_UPDATE_OVERWRITE(vp, <parameters>)
CPAR_UPDATE_NEW(vp, <parameters>)
CPAR_UPDATE_UPDATE(vp, <parameters>)

Add or replace parameters of vp. <parameters> should consist of a list of CPAR_ADD_*() calls, listed in the section called “Creating a parameter table”

The merge mode of the CPAR_ADD_MERGE() or CPAR_ADD_TRANSFER() functions overrules the merge mode of CPAR_UPDATE().

Merge using CPAR_MERGE

cpar tables can be merged using the CPAR_MERGE*() functions.

cpar_table *vp_to, *vp_from;

CPAR_MERGE(vp_to, vp_from)
CPAR_MERGE_OVERWRITE(vp_to, vp_from)
CPAR_MERGE_NEW(vp_to, vp_from)
CPAR_MERGE_UPDATE(vp_to, vp_from)

These functions copy the parameters of vp_from into vp_to. CPAR_MERGE() uses the NO_OVERWRITE merge mode, and the other functions use the obvious merge modes.

Merge using CPAR_ADD_MERGE

A cpar table can be merged into a cpar table being created with CPAR_CREATE_PARAMETERS() using the CPAR_ADD_MERGE*() functions. This also applies to CPAR_UPDATE(). In the case of CPAR_UPDATE(), the merge mode of the CPAR_UPDATE*() function is overridden by the merge mode of the CPAR_ADD_MERGE*() function.

cpar_table *vp;


CPAR_ADD_MERGE() uses the NO_OVERWRITE merge mode, and the other functions use the obvious merge modes.

An example, assuming vp1 and vp2 are previously created cpar tables, and you want to merge vp2 into vp1, along with an addition to vp1:

    CPAR_ADD_INT("new parameter", 22),

Note that if "new parameter" was already in vp1, it would be overwritten, but if any of the parameters in vp2 were in vp1, the program would abort with an error.


You may want to merge the value of one parameter into a cpar table using a different key. This is done using the CPAR_ADD_TRANSFER*() functions.

CPAR_ADD_TRANSFER(to_key, from_vp, from_key)
CPAR_ADD_TRANSFER_OVERWRITE(to_key, from_vp, from_key)
CPAR_ADD_TRANSFER_NEW(to_key, from_vp, from_key)
CPAR_ADD_TRANSFER_UPDATE(to_key, from_vp, from_key)

CPAR_ADD_TRANSFER() uses the NO_OVERWRITE merge mode, and the other functions use the obvious merge modes. The merge mode is relative to to_key. If from_key doesn't exist in from_vp, do nothing.

These functions are used with CPAR_UPDATE(). Suppose vp1 has a parameter `vp1 parameter', and vp2 has the parameter `vp2 parameter'. You want to merge the value in `vp2 parameter' into `vp1 parameter':

cpar_table *vp1, *vp2;

    CPAR_ADD_TRANSFER_OVERWRITE("vp1 parameter", vp2, "vp2 parameter"));

Notice that the merge mode of CPAR_ADD_TRANSFER_OVERWRITE() overrides the merge mode of CPAR_UPDATE(), so there will be no error even if `vp1 parameter' already exists in vp1.

Keep and Remove

CPAR_KEEP(vp, <keys>)

Only keep the parameters with keys from <keys>, where <keys> is a variable list of char* strings.

CPAR_KEEP_MATCH(vp, <string>)

Only keep the parameters with keys that start with <string>, where <string> is a single char* string (case sensitive).

CPAR_REMOVE(vp, <keys>)

Remove parameters with keys from the list <keys>, where <keys> is a variable list of char* strings.

CPAR_REMOVE_MATCH(vp, <string>)

Only remove the parameters with keys that start with <string>, where <string> is a single char* string (case sensitive).


The "seen" flag

The purpose of the "seen" flag is to keep track of whether or not parameters have been written or read. An example of where this is useful: at the beginning of a function, you may want to make sure that the function has dealt with all the parameters that were passed to it. If you cleared the "seen" flag at the beginning of the parameter, you can test later to see if all the parameters have been read or written.

CPAR_CLEAR_SEEN(vp, <keys>)
CPAR_CLEAR_SEEN_MATCH(vp, <match string>)

CPAR_SET_SEEN(vp, <keys>)
CPAR_SET_SEEN_MATCH(vp, <match string>)

CPAR_IS_ALL_SEEN(vp, verbose)
CPAR_IS_SEEN(vp, verbose, <keys>)
CPAR_IS_SEEN_MATCH(vp, verbose, <match string>)

CPAR_CLEAR_ALL_SEEN() clears the seen bit for all parameters in vp, CPAR_CLEAR_SEEN() clears the parameters with keys in the list <keys>, and CPAR_CLEAR_SEEN_MATCH() clears the parameters with keys that start with the string <match string>.

CPAR_SET_ALL_SEEN() sets the seen bit for all parameters in vp, CPAR_SET_SEEN() sets the parameters with keys in the list <keys>, and CPAR_SET_SEEN_MATCH() sets the parameters with keys that start with the string <match string>

CPAR_IS_ALL_SEEN() returns true if all the parameters in vp have the seen flag set, CPAR_IS_SEEN() returns true if all the parameters in the key list <keys> have the seen flag set, and CPAR_IS_SEEN_MATCH() returns true if all the parameters whose keys start with <match string> have the seen flag set.

The "default" flag

The purpose of the "default" flag is to provide default values for parameters. The functions dealing with the default flag are identical to the seen flag functions, with "SEEN" replaced by "DEFAULT" in the function name. They are identical otherwise.

Flag Rules

Flag rules common to the default and seen flags during merge and transfer:

  • If a parameter only exists in one of the two parameter lists, the flags are copied to the new location.

Default flag merge and transfer rules:

  • When merging a non-default parameter into a default parameter, the default parameter is always overwritten without error, regardless of the merge mode.

  • When merging a default parameter into a non-default parameter, the non-default parameter is never overwritten, regardless of the merge mode.

  • When merging a non-default parameter into a non-default parameter, normal merge rules apply.

  • When merging a default parameter into a default parameter, normal merge rules apply.

Seen flag merge and transfer rules:

  • The seen flag does not affect merge and transfer, and normal merge rules apply.

Command-line processing

cpar can be used as a very easy command-line processor. How to do it is probably best illustrated with code:

int main(int argc, char *argv[])
cpar_table *command_line_options = CPAR_CREATE_EMPTY_PARAMETERS();
CPAR_PROCESS_COMMAND_LINE(command_line_options, argc, argv,
    "-n",              CPAR_BOOL,
    "--long-n-option", CPAR_BOOL,
    "-s",              CPAR_STRING,
    "-k",              CPAR_DOUBLE);

printf("This program was called with name %s \n", 
    CPAR_GET_STRING(command_line_options, "program_name"));
if (CPAR_GET_BOOL(command_line_options, "-n") 
    || CPAR_GET_BOOL(command_line_options, "--long-n-option")) 
    printf("-n or --long-n-option was used \n");
if (CPAR_EXISTS(command_line_options, "-s"))
    char *s = CPAR_GET_STRING(command_line_options, "-s");
if (CPAR_EXISTS(command_line_options, "-k"))
    double *k = CPAR_GET_DOUBLE(command_line_options, "-k");
if (CPAR_EXISTS(command_line_options, "commands"))
    printf("commands after options = %s \n", 
        CPAR_GET_STRING(command_line_options, "commands"));
    printf("first command is argv[%d] \n", 
        CPAR_GET_INT(command_line_options, "commands_first_index"));

If the above program was called with:

./example -n -s test_string -k 23.5 command1 -a

Then `example' is the program name, `-n', `-s', and `-k' are options, `test_string' and `23.5' are option arguments, and `command1' and `-a' are commands.

The cpar macro function CPAR_PROCESS_COMMAND_LINE() takes as many pairs of string,type as you want. For all types except CPAR_BOOL, if the command-line option is missing, the corresponding parameter in the cpar table will not exist. That is why the CPAR_EXISTS() calls are needed in the code above. For type CPAR_BOOL, the parameter will always exist: it is true if the option was given on the command-line, and false if it wasn't. The special parameter "program_name" is set with the name used to call the program.

Options are identified by starting with a `-'. The first command-line argument that doesn't start with `-' is the first command. Also, you will get a warning if the first character of the argument after a non-boolean option is a `-'. For example, if the program above was run as "./example -s -test_string", you will get a warning. This is to catch the case of accidentally forgetting the argument after the option. The first option on the command-line that doesn't begin with a `-' is assumed to be the first command. If there are commands, then the parameter "commands" contains a string consisting of all the commands concatenated with a space between each one. The int parameter "commands_first_index" contains the index of argv[] for the first command.

The seen flag is cleared for all options parameters after the call to CPAR_PROCESS_COMMAND_LINE(). The built-in parameters "program_name", "commands", and "commands_first_index" have the seen flag set. This allows a simple check using CPAR_IS_ALL_SEEN() at the end of command-line processing to make sure you did something with all the command-line options.

Empty and NULL parameter tables


Returns an empty parameter table in vp.

Most cpar functions silently return if passed a parameter table with value NULL. This is to allow a calling function to use no extra parameters by just passing the NULL value instead of a CPAR_CREATE_EMPTY_PARAMETERS(). The exceptions are the functions that expect a parameter table:

  • CPAR_GET_*() functions

  • CPAR_MERGE*() functions

  • CPAR_UPDATE*() functions

These functions will abort with an error message if passed a NULL parameter table.

The "real" type

CPAR lets you use a type called "real" for your floating point parameters. This allows you to change your real type to float, double, or long double with just a recompile. This type is defined at compile-time by setting one of the following the preprocessor variables:


The default type for real if none of these are defined is double. There are things to watch out for though. Because of the weird way ISO C "promotes" arguments in a variable parameter list, all floats are converted to double when passed using CPAR_ADD_GENERIC(). They are stored in the correct type by the CPAR library, however. Also, if the "real" type is long double and you pass a number using

CPAR_ADD_GENERIC("some key", CPAR_REAL, 1.2E5)

The number will be passed on the function argument list as a double, but then read by cpar as a long double. Can you say "segmentation fault"? So be sure to typecast the number:



CPAR_ADD_GENERIC("some key", CPAR_REAL, (real)(1.2E5))

or use the CPAR_ADD_REAL() function which promotes the type correctly.

The "string" type

The CPAR "string" type is similar to the "char pointer" type, except that a copy of the string is made when the parameter is added. It is freed when the cpar_table is freed. Use "char pointer" when you want to be in control of the string memory management, use "string" if you want CPAR to be in charge of it.

For example, in the following code:

void f(cpar_table *vp)
char local_string[20] = "a string";
    CPAR_ADD_CHAR_POINTER("test1", local_string));

void main(void)
printf("test1 = %s\n", CPAR_GET_CHAR_POINTER(vp, "test1"));

vp will hold a pointer to a string that no longer exists after the function returns, so the printf statement will return garbage or segfault. If the string type was used, the code would work as expected.


cpar_table *vp;
CPAR_EXISTS(vp, <keys>)

Return true if all the keys in <keys> are in parameter table vp, false otherwise.

Example code

See the file example.c in the source directory for an example of all cpar features. This code is mainly intended to be a compile-time test of the library, so it is not very easy to read. For simple example of cpar code, see the section called “Simple example” and the section called “Custom pointer types”.

Chapter 3. Advanced Topics

Custom pointer types

cpar has a limited number of variable types built in. To extend cpar you can create your own pointer types. To create your own pointer types:

  • Create a global unsigned int variable for each new pointer type, and initialize it to CPAR_TYPE_UNINITIALIZED.

  • Put an "extern" version of the global variable in a header file that all files using the new type can see.

  • In your main() routine, before using the new type, initialize it with CPAR_ASSIGN_POINTER_TYPE().

  • In the header file, create a preprocessor macro that can create a variable of your type. This macro must be the name of the global preprocessor index variable with "_TYPECAST" appended.

An example:

/* example.h */
struct test_struct {
  int a;
  double b;
extern unsigned int custom_long_double;
extern unsigned int custom_struct;
extern unsigned int custom_function;
#define custom_long_double_TYPECAST(var)  long double *var
#define custom_struct_TYPECAST(var)  struct test_struct *var
#define custom_function_TYPECAST(var)  int (*var)(cpar_table *vp)

/* example.c */
unsigned int custom_long_double = CPAR_TYPE_UNINITIALIZED;
unsigned int custom_struct = CPAR_TYPE_UNINITIALIZED;
unsigned int custom_function = CPAR_TYPE_UNINITIALIZED;

void subroutine(cpar_table *vp)

u = CPAR_GET_GPOINTER(vp, "key 0", custom_long_double);
t = CPAR_GET_GPOINTER(vp, "key 1", custom_struct);
*u = t->b * t->a;

void main(void)
cpar_table *vp;
long double ld = 0.0L;
struct test_struct s;

s.a = 2;
s.b = 3.4;
a_function = subroutine;
        CPAR_ADD_GPOINTER("key 0", custom_long_double, &ld),
        CPAR_ADD_GPOINTER("key 1", custom_struct, &s)
        CPAR_ADD_GPOINTER("key 2", custom_function, a_function)

Because the cpar library appends "_TYPECAST" to the global variable holding your custom type, the literal string of your global variable is important, and you must always use the same name.

Store by value

For gpointer types, there are two ways to store a parameter in cpar: by value and by reference. By value is when the parameter contains the pointer along with the value pointed to, and by reference is when the parameter just contains the pointer to the value. Once you decide to store the value pointed to by a pointer, you have to know more about what you're pointing to. For a simple pointer, like a char pointer to a string, you only need to know the size. This would also be true of a struct like:

struct example {
  char s[50];
  int t;

because there are no pointers inside the example struct. So for simple cases like this, you only need to specify the size of the value stored, and use this function instead of CPAR_ADD_GPOINTER:

CPAR_ADD_SV_GPOINTER(key, gpointer_type, pointer, size)

If the struct you're pointing to has pointers itself, though, it is obviously more complicated to store all the values pointed to by those pointers, because cpar doesn't know anything about your structure. For these cases, you need to write three functions and pass them to cpar so it will know what to do with your structure:

  • allocate function - allocates all the memory needed by your structure

  • copy function - copies the contents of one structure to another

  • free function - frees all the memory used by a structure

Once you write these functions, you use

CPAR_ADD_CV_GPOINTER(key, gpointer_type, pointer, 
                     allocate_func, copy_func, free_func)

within a CPAR_CREATE_PARAMETERS() or CPAR_UPDATE() call to add your structure to the parameter table.


There are a few options when debugging a problem related to the cpar library:

Add the line:

#define CPAR_DEBUG

to cpar.h to turn on internal debugging

Use CPAR_LIST(vp) to print a human-readable version of vp to CPAR_ERROR_STREAM (defined in cpar_internal.h as stderr by default)