13

I have a function that takes a void** argument and an integer that indicates its datatype

void foo (void** values, int datatype)

Inside the function, depending on the datatype, I malloc it this way:

if (datatype == 1)
    *values = (int*) malloc (5 * sizeof(int));
else if (datatype == 2)
    *values = (float*) malloc (5 * sizeof(float));

All is good upto now. However, when character strings come into the picture, things get complicated. The void** would need to be void***, since I will need to do something like this:

*values = (char**) malloc (5 * sizeof(char*));
for(i=0;i<5;i++)
    (*values)[i] = (char*) malloc (10);
..
strncpy( (*values)[0], "hello", 5);

How should such a situation be handled? Can I pass a char*** to the function that expects a void** but cast it correctly inside it?

void foo (void** values, int datatype) {

if(datatype == 3) {
    char*** tmp_vals = (char***) values;
    *tmp_vals = (char**) malloc (5 * sizeof(char*));
    ...
    (*tmp_vals)[i] = (char*) malloc (10 * sizeof(char));
    strncpy (  (*tmp_vals)[i], "hello", 5);
}

So I just cast the void** into a char***. I tried this and ignoring the warnings, it worked fine. But is this safe? Is there a more graceful alternative?

Korizon
  • 3,677
  • 7
  • 37
  • 52
  • Where are strings contents stored? Just allocating an array of pointers doesn't create space for characters. Are strings dynamic and allocated in the same place, or static, or an arbitrary mix? – Seva Alekseyev Oct 03 '13 at 22:25
  • The same function copies data into the newly created array of strings – Korizon Oct 03 '13 at 22:27
  • But you don't create array of strings... – zubergu Oct 03 '13 at 22:27
  • 1
    Let me rephrase: what are those `char` pointers that you allocate finally pointing at? – Seva Alekseyev Oct 03 '13 at 22:29
  • @SevaAlekseyev i just edited the question to make it more clear. – Korizon Oct 03 '13 at 22:33
  • 6
    YES! we have a three star void programmer! – wildplasser Oct 03 '13 at 22:34
  • @wildplasser No, I still have void** , not 3 stars :-) – Korizon Oct 03 '13 at 22:37
  • What the need for such a generic function? Is datatype typically determined at runtime? – goji Oct 03 '13 at 22:43
  • @Troy I need a generic function to avoid foo_int, foo_float, foo_double, foo_string etc. One way would be to have these foo_xxx wrappers and put the rest of the common code of the function in another function .. – Korizon Oct 03 '13 at 22:47
  • the foo_xxx way of implementing this may create a large interface, but it will be more efficient, more explcit and the implementation less complicated. – goji Oct 03 '13 at 22:59
  • You should take a look at any of the *print*() implementations that use va_arg, va_list ... its not just for variable number of arguments, the types can be different too – technosaurus Oct 03 '13 at 23:25
  • I would look into how the various dynamic language interpreters handle it. Perl, Python, Ruby, etc. They each have values that can be integer, float, string, objects or lists, tuples, arrays. So their source code has several good ideas. – Zan Lynx Oct 03 '13 at 23:36
  • @ZanLynx Most of the ones I have seen used a union of structs with var->type and var->value where type is usually an int (or bit field if additional flags are used in the other bits) and value is a different type in each unioned struct. That would work too. – technosaurus Oct 04 '13 at 01:01
  • @KVM are you required to use a void ** due to an external call or can you use existing standard C constructs that do what you are trying to do, such as va_arg? http://www.tutorialspoint.com/c_standard_library/c_macro_va_arg.htm – technosaurus Oct 04 '13 at 02:37
  • `foo()` is basically an api that i am designing, so I have to decide if it should accept a `void*` or a `void**` etc. I dont need variable args, the no. of arguments is fixed. The main concern was how many stars should follow the `void` – Korizon Oct 04 '13 at 02:42
  • 1
    @KVM Note [`void*` is generic. but `void**` is not](http://stackoverflow.com/questions/18951824/using-and-dereferencing-void/18951881#18951881) – Grijesh Chauhan Oct 04 '13 at 02:46
  • @troy "What the need for such a generic function? Is datatype typically determined at runtime?" -- foo is actually a read_from_file function, where the user provides at runtime the name of the file he wants to read from and the type of data in it. – Korizon Oct 04 '13 at 15:03

5 Answers5

7

How should such a situation be handled? Can I pass a char*** to the function that expects a void** but cast it correctly inside it?

No, that's technically Undefined Behavior. It may appear to work on your computer, but it may fail on some future computer that implements different pointer types with different representations, which is allowed by the C language standard.

If your function expects a void**, then you better pass it a void**. Any pointer type can be implicitly converted to void*, but that only works at the top level: char* can be converted to void*, and char** can be implicitly converted to void* (because char** is "pointer to char*"), but char** cannot be converted to void**, and likewise char*** also cannot be converted to void**.

The proper way to call this function is to pass it a proper void**, then cast the resulting void* pointer back to its original type:

void foo(void **values, int datatype)
{
    if(datatype == 3)
    {
        char ***str_values = ...;
        *values = str_values;  // Implicit cast from char*** to void*
    }
    else
    ...
}

...

void *values;
foo(&values, 2);
char ***real_values = (char ***)values;

Assuming that *values was actually pointed to a char***, then this cast is valid and does not have any Undefined Behavior in any of the code paths.

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • Hmm. This doesn't match up with my understanding, but I'm not intimately familiar with the standard. Can you provide a reference to indicate how `char **values; foo(&values, 2)` is undefined behavior while your example is not? I'm not trying to claim that you're wrong, just trying to figure out where the gap in my understanding is. – Brian Campbell Oct 03 '13 at 22:50
  • 1
    @BrianCampbell: In Adam's example, `&values` is type `void **`, which the function accepts, so there is no conversion. In your example, `&values` is type `char ***` which obviously differs from type `void **`. C11 6.3.2.3.7 says "A pointer to an object type may be converted to a pointer to a different object type. if the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.` – Crowman Oct 03 '13 at 22:58
  • 1
    Its undefined behavior because an implementation is allowed to have multiple different representations for pointers, depending on the pointed at type. To avoid undefined behavior, you need to actually convert the whatever pointers to or from `void *` pointers -- you can't just convert the `void **` and expect the pointed at pointers to have their types converted. But MOST implementations don't have multiple pointer representations, so everything is all right. – Chris Dodd Oct 03 '13 at 23:01
  • Right. Technically this isn't undefined behavior on an implementation where the resulting pointer *is* correctly aligned for the referenced type, but since truly portable code can't predict whether that'll be true on any given implementation, you may as well treat it as always being undefined. – Crowman Oct 03 '13 at 23:12
  • That says that you can convert pointers that have the same alignment requirements, but if the pointers have different alignment requirements then the behavior is undefined. That then brings two new questions: does a `char **` have the same alignment requirements as a `void *` (if it does, then a `char ***` could be converted to a `void **` and back just fine). The standard says: "A pointer to void shall have the same representation and alignment requirements as a pointer to a character type", but that doesn't save us, this is a `void *` being compared to `char **`. So that doesn't help. – Brian Campbell Oct 03 '13 at 23:40
  • But the second question is why Adam's example is not undefined behavior under the same rules. Within `foo` we're converting a `char ***` to a `void *` and then converting it back again in the calling code. This seems like it would hit the exact same undefined behavior that my example would hit. – Brian Campbell Oct 03 '13 at 23:42
  • 1
    @BrianCampbell: You're guaranteed to be able to safely convert between `void *` and any other pointer to object type under C11 6.3.2.3.1. So while converting between, say, `char ***` and `void **` is *not* guaranteed to be OK, converting between `char ***` and `void *` *is* guaranteed to be OK. `void *` is the only pointer type for which this is guaranteed to be OK. – Crowman Oct 03 '13 at 23:48
  • So can you theoretically have a pointer value that actually changes its value when cast to a different pointer type in order to get proper alignment? How would such implementations handle malloc(), because malloc() doesn't know what pointer type it's returning, right? If you cast the result from malloc() to a pointer type with unique alignment and it needs to change value, exactly how does that work? Does malloc() automatically include an alignment cushion on such systems? Or does it always return the "lowest-common-denominator" for all possible alignments? – willus Oct 04 '13 at 00:04
  • @willus: `malloc()` *does* know what pointer type it's returning, though - it returns a `void *`. The *value* shouldn't differ, just the alignment. In the case of conversion of, say, `void *` to `double *` if they were aligned differently, the implementation knows how a `double *` is aligned, and it knows how a `void *` is aligned, so it can convert between the two at the point at which the conversion happens, which is after `malloc()` returns, so `malloc()` doesn't need to know the details. – Crowman Oct 04 '13 at 00:14
  • @PaulGriffiths--Let's say I do malloc(8) and it returns address 3. But then I cast it to (double *) and it needs to be aligned on a multiple of 8. Now I don't have enough room to store my double precision value. Or do I? If so, how? – willus Oct 04 '13 at 00:23
  • 1
    @willus: OK, I see what you're saying now. The conversion clauses deal with conversion between `void *` and other pointer types, so when converting from, say, a `double *` to a `void *`, you already have a `double *` that's correctly aligned to begin with, and `void *` has to be able to store it and give it back. In your example with `malloc()`, if there were such alignment requirements, I would think it would have to be written such that it only returned pointers which satisfied any and all alignment requirements. – Crowman Oct 04 '13 at 00:30
  • @PaulGriffiths--in which case I would think most of this discussion is academic, but very interesting nonetheless. I just answered my own question, I think. I malloc'd several different memory sizes (consecutively) on gcc/i386 and they all return values which are multiples of 16. – willus Oct 04 '13 at 00:33
  • @willus: But consider where `void **` has a 4 byte alignment, and `double` has an 8 byte alignment, and you have an `void **` that points to address 4. You'd get undefined behavior when converting from `void **` to `double *`, but `void *` is guaranteed to not put you in this situation. – Crowman Oct 04 '13 at 00:36
  • @PaulGriffiths--but (1) the examples here all use malloc(), which appears to use the most generous alignment needed, and (2) all of these examples are converting void** to some other ** type (pointer to a pointer). Which is why I suggested that this is mostly an academic discussion. – willus Oct 04 '13 at 00:54
  • @willus: Partially they use `malloc()`, you're right, but in all the examples given, there's also a conversion happening when a pointer is passed to `foo()` in the first place, so this problem could arise before you ever get to calling `malloc()`. Indeed, the original question was mainly referring to whether it was OK to ignore the warnings from converting a `void **` to a `char ***` *before* the call to `malloc()`. – Crowman Oct 04 '13 at 00:57
  • > "char* can be converted to void*, and char** can be implicitly converted to void* (because char** is "pointer to char*"), but char** cannot be converted to void**" Wow, I did not know that char** cannot be converted to void**. – Korizon Oct 04 '13 at 02:32
5

A void * is just a pointer to an unspecified type; it could be a pointer to an int, or a char, or a char *, or a char **, or anything you wanted, as long as you ensure that when you dereference, you treat it as the appropriate type (or one which the original type could safely be interpreted as).

Thus, a void ** is just a pointer to a void *, which could be a pointer to any type you want such as a char *. So yes, if you are allocating arrays of some types of objects, and in one case those objects are char *, then you could use a void ** to refer to them, giving you something that could be referred to as a char ***.

It's generally uncommon to see this construction directly, because usually you attach some type or length information to the array, rather than having a char *** you have a struct typed_object **foo or something of the sort where struct typed_object has a type tag and the pointer, and you cast the pointer you extract from those elements to the appropriate types, or you have a struct typed_array *foo which is a struct that contains a type and an array.

A couple of notes on style. For one, doing this kind of thing can make your code hard to read. Be very careful to structure it and document it clearly so that people (including yourself) can figure out what's going on. Also, don't cast the result of malloc; the void * automatically promotes to the type its assigned to, and casting the result of malloc can lead to subtle bugs if you forget to include <stdlib.h> or your update the type declaration but forget to update the cast. See this question for more info.

And it's generally a good habit to attach the * in a declaration to the variable name, not the type name, as that's how it actually parses. The following declares one char and one char *, but if you write it the way you've been writing them, you might expect it to declare two char *:

char *foo, bar;

Or written the other way:

char* foo, bar;
Community
  • 1
  • 1
Brian Campbell
  • 322,767
  • 57
  • 360
  • 340
3

You don't need to (and probably shouldn't) use a void ** at all - just use a regular void *. Per C11 6.3.2.3.1, "a pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer." A pointer variable, including a pointer to another pointer, is an object. void ** is not "a pointer to void". You can convert freely and safely to and from void *, but you're not guaranteed to be able to convert safely to and from void **.

So you can just do:

void foo (void* values, int datatype) {
    if ( datatype == 1 ) {
        int ** pnvalues = values;
        *pnvalues = malloc(5 * sizeof int);

    /*  Rest of function  */
}

and so on, and then call it similar to:

int * new_int_array;
foo(&new_int_array, 1);

&new_int_array is of type int **, which will get implicitly converted to void * by foo(), and foo() will convert it back to type int ** and dereference it to indirectly modify new_int_array to point to the new memory it has dynamically allocated.

For a pointer to an dynamic array of strings:

void foo (void* values, int datatype) {

    /*  Deal with previous datatypes  */

    } else if ( datatype == 3 ) {
        char *** psvalues = values;
        *psvalues = malloc(5 * sizeof char *);
        *psvalues[0] = malloc(5);

    /*  Rest of function  */
}

and so on, and call it:

char ** new_string_array;
foo(&new_string_array, 3);

Similarly, &new_string_array is type char ***, again gets implicitly converted to void *, and foo() converts it back and indirectly makes new_string_array point to the newly allocated blocks of memory.

Crowman
  • 25,242
  • 5
  • 48
  • 56
  • OP wants the function `foo` to allocate the memory and return that allocated memory via an out parameter, not require the calling function to allocate memory. So you need a `void**` so that you can dereference it and return the address back to the caller. – Adam Rosenfield Oct 03 '13 at 22:46
  • @AdamRosenfield: If the first argument he's passing is an address of a pointer in the caller, you don't need a `void **`, a regular `void *` will do. Any function can dynamically allocate memory for its caller by accepting the address of a pointer. If the calling function did `int * allocate_me; foo(&allocate_me, 1);` then my first example will work as required. `&allocate_me` is of course of type `int **`, which will implicitly convert to `void *`, and then `foo()` converts it back to `int **` and indirectly modifies `allocate_me`. – Crowman Oct 03 '13 at 22:48
  • @AdamRosenfield: The point of using a `void *` is to avoid the undefined behavior you note. If `foo` takes a `void *` that might really be a `float **` or `int **` or `char ***` depending on the `datatype` argument, it can cast it to the right thing a then store through it, storing the type of pointer that the caller actually expects. – Chris Dodd Oct 03 '13 at 23:05
1

There is a builtin mechanism to do this already with the added bonus that it allows a variable number of arguments. It is commonly seen in this format yourfunc(char * format_string,...)

/*_Just for reference_ the functions required for variable arguments can be defined as:
#define va_list             char*
#define va_arg(ap,type)     (*(type *)(((ap)+=(((sizeof(type))+(sizeof(int)-1)) \
                                & (~(sizeof(int)-1))))-(((sizeof(type))+ \
                                (sizeof(int)-1)) & (~(sizeof(int)-1)))))
#define va_end(ap)          (void) 0
#define va_start(ap,arg)    (void)((ap)=(((char *)&(arg))+(((sizeof(arg))+ \
                                (sizeof(int)-1)) & (~(sizeof(int)-1)))))
*/

So here is a basic example that you could use with a format string and variable number of args

#define INT '0'
#define DOUBLE '1'
#define STRING '2'

void yourfunc(char *fmt_string, ...){
  va_list args;
  va_start (args, fmt_string);
  while(*fmt_string){
    switch(*fmt_string++){
     case INT: some_intfxn(va_arg(ap, int));
     case DOUBLE: some_doublefxn(va_arg(ap, double));
     case STRING: some_stringfxn(va_arg(ap, char *));
     /* extend this as you like using pointers and casting to your type */
     default: handlfailfunc();
    }
  }
  va_end (args);
}

So you can run it as: yourfunc("0122",42,3.14159,"hello","world"); or since you only wanted 1 to begin with yourfunc("1",2.17); It doesn't get much more generic than that. You could even set up multiple integer types to tell it to run a different set of functions on that particular integer. If the format_string is too tedious, then you can just as easily use int datatype in its place, but you would be limited to 1 arg (technically you could use bit ops to OR datatype | num_args but I digress)

Here is the one type one value form:

#define INT '0'
#define DOUBLE '1'
#define STRING '2'

void yourfunc(datatype, ...){ /*leaving "..." for future while on datatype(s)*/
  va_list args;
  va_start (args, datatype);
  switch(datatype){
     case INT: some_intfxn(va_arg(ap, int));
     case DOUBLE: some_doublefxn(va_arg(ap, double));
     case STRING: some_stringfxn(va_arg(ap, char *));
     /* extend this as you like using pointers and casting to your type */
     default: handlfailfunc();
  }
  va_end (args);
}
technosaurus
  • 7,676
  • 1
  • 30
  • 52
0

With some tricks, you can do it. See example:

int sizes[] = { 0, sizeof(int), sizeof(float), sizeof(char *) }

void *foo(datatype) {
   void *rc = (void*)malloc(5 * sizes[datatype]);
   switch(datatype) {
     case 1: {
       int *p_int = (int*)rc;
       for(int i = 0; i < 5; i++)
         p_int[i] = 1;
     } break;
     case 3: {
       char **p_ch = (char**)rc;
       for(int i = 0; i < 5; i++)
         p_ch[i] = strdup("hello");
     } break;
   } // switch
   return rc;
} // foo

In the caller, just cast returned value to appropriate pointer, and work with it.

olegarch
  • 3,670
  • 1
  • 20
  • 19
  • 3
    This `void *rc = (void*)malloc(...` takes a `void *`, and casts it to a `void *` in order to store it in a `void *`. Casting the return from `malloc()` isn't great at the best of times, but this is taking it to extremes. – Crowman Oct 03 '13 at 23:07