I am learning C and in many places I find that people use func(void *ptr) as an argument to a function. I understand that void *ptr is used when you are not sure of the type of data you will send and would like to leave open. The problem is that I see this used in places where, I think at least, the type of data will never change.
For example I was looking at the libcurl API and noticed this example to save some the response of a URL request. In the example they created a struct:
struct MemoryStruct {
char *memory;
size_t size;
};
Then they defined a callback function which takes multiple arguments most notable the last argument:
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
But what's confusing is inside this function definition they created a new variable *mem of type MemoryStruct and casted userp to MemoryStruct.
But why all effort? Since we know we are going to provide a MemoryStruct argument, why couldn't they have written:
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, MemoryStruct *userp)
and do away with the whole void+casting stuff.
I have seen this in other projects as well and I feel like I am missing something as to why void *ptr is being used so often even when it isn't needed...
struct MemoryStruct
cannot be in the declaration of WriteMemoryCallback
because WriteMemoryCallback
must be passed to a cURL routine, and cURL does not know anything about struct MemoryStruct
.
For example I was looking at the libcurl API and noticed this example…
That example passes WriteMemoryCallback
to curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
.
Digging into some header files for cURL, curl_easy_setopt
is effectively declared as CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);
. So it could accept a pointer to WriteMemoryCallback
regardless of its type, since ...
will accept any type. However, to make use of that pointer, it must fetch the value passed for the argument, with C’s <stdarg.h>
features or equivalent, and it must use that value as some function pointer type when making the function call.
In other words, it has some code similar to:
// Define the type of the callback function.
typedef size_t Function(void *contents, size_t size, size_t nmemb, SomeType *userp);
// Get the pointer to the function the user passed.
Function *Pointer = va_arg(ap, Function *);
// Call the user’s callback function.
Pointer(contents, size, nmemb, userp);
Now here is the problem: That SomeType
above cannot be struct MemoryStruct
because the cURL source code does not know anything about struct MemoryStruct
. That structure was defined in the code calling cURL. That code did not exist when cURL was written. There is no way for the cURL author to know what type the user would use.
So the cURL code uses void
for SomeType
.
Now, given that, the user could declare WriteMemoryCallback
to have a parameter of type struct MemoryStruct *
and then could pass the address of WriteMemoryCallback
to curl_easy_setopt
, and curl_easy_setopt
would use it with a type containing a parameter of type void *
instead of struct MemoryStruct *
. That would actually work in many C implementations. However, it is not proper C code. The C standard does not define what happens when you pass a pointer of the former type to a routine that uses it as a value of the latter type. Technically, the pointer types struct MemoryStruct *
and void *
could have different representations or even different sizes, and then the call Pointer(contents, size, nmemb, userp)
would not work. And the two types of pointer-to-function could have different representations and/or sizes, in which case the va_arg
macro would not work.
Neither of these is generally the case in modern C implementations, but it is allowed by the C standard. So it is generally better to write code that is supported by the C standard, using void *
both in the cURL code and in the user code.
The person writing that example code knows what type WriteMemoryCallback
will use, and that is why they can write code in WriteMemoryCallback
that assigns the pointer to the known type. However, they have to pass WriteMemoryCallback
through the cURL code, so they cannot use any custom-defined types in its declaration.