1

I recently wrote a wrapper for LevelDB in C and stumbled about the following problem. The LevelDB function to store data in a database looks like this:

leveldb_put(leveldb_t* db, const leveldb_writeoptions_t* options, const char* key, size_t keylen, const char* val, size_t vallen, char** errptr);

For the key and value, they use a char*. That means I would have to cast arguments that aren't char pointers. This happens often because I often store structs in the database.

After thinking about this I decided to use a void* for key and data in my wrapper function. It then looks something like this:

int db_put(db_t db, void *key, size_t keylen, void *value, size_t valuelen)
{
    char *k = (char*)key;
    char *v = (char*)value;

    /* Call leveldb_put() here with k and v as parameters. */

    return 0;
}

This way I don't have to cast the arguments I pass to my db_put() function. I think this solution is more elegant, but I guess LevelDB knew what they were doing when they choose the char pointers.

Is there a reason not to use void* to pass arbitrary data to a function?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
lkamp
  • 193
  • 1
  • 10
  • 1
    possible duplicate of [void\* or char\* for generic buffer representation?](http://stackoverflow.com/questions/3813138/void-or-char-for-generic-buffer-representation) – edmz Jun 24 '15 at 10:08
  • Clarification: `leveldb_put()` does not take "arbitrary data", but a pointer to "arbitrary data". – chux - Reinstate Monica Jun 24 '15 at 13:25

4 Answers4

4

Is there a reason not to use void* to pass arbitrary data to a function?

No. In fact, void * exists to facilitate passing arbitrary data without the need for ugly casting. That's why ptr-to-void was standardized. In C at least. C++ is a different beast.

At LevelDB they have to deal with historical code born with char * , or pre C89 compilers, or any other veiled reason causing refactoring-inertia. Their code would work with ptrs-to-void just as well.

Note that in your version of db_put the casts should be removed as they are redundant.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Jens
  • 69,818
  • 15
  • 125
  • 179
0

A void* can be considered a black box that holds a pointer. By holding it in a void* you are effectively saying that you don't care or it contains at that point, so this will allow you to make any assumption about it. Void* is a "true" generic pointer, and can be directly assign to any particular data type without using cast.

Meanwhile, a char* explicitly specify the type of the respective object. Initially there was no char* and char* was also used to represent generic pointers. When char* is used an explicit cast is required, however the usage of char* as a generic pointer is not recommanded, because it may create confusion, like it did back there when it was hard to tell if a char* contains a string or some generic data.

Also, is legal to perform arithmeticon a char*, but not on a void*.

The downside of using void*, is given by their main usage, they can hide the actual type of the data you're storing, which prevents the compiler and other stuff to detect type errors.

In your specific situation there is no problem in using void* instead of char*, so you can use void* without worries.

Edit: Updated and reformuled the answer to correct some wrong info

A B
  • 497
  • 2
  • 9
  • you can cast _any_ pointer type to any other pointer type: 6.3.2.3 Pointers: _"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."_ meaning you can cast any pointer to `void *`, and because `void *`, which can be cast again to any ptr type. Pointer arithmetic is indeed not legal, and so is dereferencing a `void *`. IMO, the latter is why `void *` and `char *` are not funcitonally equivalent at all – Elias Van Ootegem Jun 24 '15 at 11:05
  • @EliasVanOotegem There's something wrong with what you're saying: A *function pointer* isn't a *pointer to any object type*. Hence, your suggestion that *any* pointer type can be cast to *any other* pointer type doesn't necessarily work when either the *source* or *target* type of the conversion is a function pointer. – autistic Jun 24 '15 at 11:09
  • @EliasVanOotegem What is the title of the section "J.5"? – autistic Jun 24 '15 at 11:13
  • 2
    @undefinedbehaviour: _"J.5 Common extensions"_ aw snap... that's what you get for wanting to be too clever. You're right, I'll remove the comment, perhaps add the same info a second time, this time specifying casting `void *` to function pointers is not portable – Elias Van Ootegem Jun 24 '15 at 11:19
  • _errata_: object pointers can be cast from and to `void *` safely. However, this is not guaranteed to be the case for function pointers, being able to cast object pointers to function pointers is listed in the standard as a common extension, but relying on it may cause portability issues (reference J.5.7 Function pointer casts, under J.5 Common extensions) – Elias Van Ootegem Jun 24 '15 at 11:22
  • @EliasVanOotegem You might be interested in 6.3.2.3p5: "An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation" and its corresponding p6: "Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type." – autistic Jun 24 '15 at 11:29
  • @EliasVanOotegem Perhaps the best guarantee is [#6.3.2.3p8](http://www.iso-9899.info/n1570.html#6.3.2.3p8), however: "A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined." – autistic Jun 25 '15 at 03:01
0

The current accepted answer exists only to flatter the OP; It's actually slightly invalid.

Consider that your arbitrary struct may (most likely) have padding bytes somewhere. The value of those padding bytes is indeterminate and may or may not be insignificant.

Consider what might happen if you put a struct as key that has padding bytes, and you then attempt to get the value for that key which is otherwise equal except for the padding bytes.

Consider also how you might handle pointer members, if you choose to do so in the future.

If you intend to use a struct as key, it would be a good idea to serialise it, so you can guarantee retrieval of the corresponding value without worrying about those padding bits.

Perhaps you could pass a function pointer telling your wrapper how to serialise the key into a string...

autistic
  • 1
  • 3
  • 35
  • 80
  • My bad; the question you asked seems *very different* to that (*Is there a reason not to use `void*` to pass arbitrary data to a function?*)... It's not a constraint violation, and (the `int` idea, *not* the `struct` idea) will probably work most of the time. The scary thing about it (which is the gist of my answer) is that when it fails to work, it will do so *silently*... Ye be warned; `int` may also have padding. – autistic Jun 24 '15 at 12:15
0

You should be serializing to some standard format like json instead of dealing with raw data like that. It looks very error prone unless you always assume that the arbitrary data is just a byte buffer. In which case I would use uint8_t pointer (which is an unsigned char*) and cast all data structures to it so that the routine just thinks that it is dealing with a byte buffer.

A note on void*: i almost never ever use them. Think carefully when you introduce void pointers because in most cases you can do away with the right way of doing things which takes advantage of the standard types to avoid future bugs. The only places where you should use void* is in places like malloc() where there is not really a better way.

user2826084
  • 517
  • 5
  • 11