cparameter-passingvoid-pointerstype-erasure

How can I pass several arguments through a single void pointer argument?


I have a C function with a parameter of type void *, but I want to pass several arguments to that function, as the argument for that parameter.

In Python, you can take your arguments and easily make a single tuple out of them, like so:

thread_create(v, w, x, (y, z))

but we can't do that in C.

I had an idea to pass an array of pointers to the things I want to access in the other function, but they are of different types. Is there a way to make an array of void pointers, or something like that?

I am not very familiar with C's concept of void * parameters.


Solution

  • You do the same thing as in Python: you pass an argument that's a compound object made of multiple objects. Except that because C is a low-level language, you need to manage several things manually. You need to declare a type for the compound object, you need to ensure that you're using that type consistently, and you need to manage the memory for the compound object. So it's the same conceptual core as in Python, but significantly more difficult in practice.

    C has static typing and its type system doesn't have structural aggregate types, so you need to declare a type for the data you're going to pass. Say you want to pass an integer and a character.

    typedef struct {
        int y;
        char z;
    } thread_param_t;
    

    When you call the thread creation function, you need to create an object of this type and pass a pointer to it.

    thread_param_t param = {42, "hello"};
    pthread_create(thread, attr, entry_point, &param);
    …
    pthread_join(thread, NULL);
    

    The function that runs the code of the thread takes an argument of type void*. What's void*? It's a way to do polymorphism in C. A pointer to void is a pointer that doesn't say what it points to. It can't be used directly other than to pass it around, but a pointer to any type can be converted to a pointer to void and vice versa. For example, the 4th argument to pthread_create has the type void*, and above I passed a parameter of type thread_param_t*.

    void* entry_point(void *param_arg) {
        thread_param_t *param = param_arg;
        printf("entry_point called with y=%d and z=%s\n", param->y, param->z);
        …
    }
    

    Note that C doesn't carry around type information at runtime. It's ok to convert a pointer to void* and back to the original type. It isn't ok to convert a pointer to void* and then to a pointer to a different type, but if you accidentally do this, you won't get a nice exception, you'll get a crash if you're lucky and memory corruption or other hard-to-debug behavior if you aren't.

    In addition, you need to manage the memory used by the parameter object. Above, I showed how to declare a parameter object as a local variable. If you put this in a function, then the object only exists until the function returns. (If param is declared in a block, it's only valid until the block's closing brace.) That's fine if the function waits for the thread to finish accessing the parameter object — above I showed it waiting for the thread to end. But often that's not good enough, so you'll need to allocate memory dynamically.

    thread_param_t *param;
    param = malloc(sizeof(*param));
    if (param == NULL) …;
    pthread_create(thread, attr, entry_point, param);
    

    Since the object is allocated dynamically, you'll need to call free on it at some point, presumably in entry_point after it's finished using the object.