我最近在C中为LevelDB编写了一个包装器,并且发现了以下问题。用于在数据库中存储数据的LevelDB函数如下所示:
leveldb_put(leveldb_t* db, const leveldb_writeoptions_t* options, const char* key, size_t keylen, const char* val, size_t vallen, char** errptr);
对于键和值,他们使用char*
。这意味着我必须抛出不是char
指针的参数。这通常是因为我经常将结构存储在数据库中。
考虑到这一点后,我决定在我的包装函数中使用void*
来获取密钥和数据。它看起来像这样:
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;
}
这样我就不必将我传递给我db_put()
函数的参数强制转换。我认为这个解决方案更优雅,但我想LevelDB在选择char
指针时知道他们在做什么。
是否有理由不使用void*
将任意数据传递给函数?
答案 0 :(得分:4)
是否有理由不使用void *将任意数据传递给a 功能
没有。事实上,void *
存在以便于传递任意数据而无需丑陋的投射。这就是ptr-to-void标准化的原因。至少在C中。 C ++是一个不同的野兽。
在LevelDB,他们必须处理带有char *
或C89前编译器的历史代码,或者导致重构惯性的任何其他隐藏的原因。他们的代码也适用于ptrs-to-void。
请注意,在您的db_put
版本中,应删除广告素材,因为它们是多余的。
答案 1 :(得分:0)
void *可以被认为是一个包含指针的黑盒子。抱着它 在一个空白*你有效地说你不关心或它包含在那一点,所以这将允许你做出任何假设。 Void *是一个“真正的”通用指针,可以直接分配给任何特定的数据类型,而无需使用强制转换。
同时,char *明确指定相应对象的类型。最初没有char *和char *也用于表示通用指针。当使用char *时,需要使用显式强制转换,但是不建议使用char *作为通用指针,因为它可能会产生混淆,就像在那里很难判断char *是否包含字符串或一些通用数据。
此外,在char *中执行算术是合法的,但在void *上则不合法。
使用void *的缺点是它们的主要用途,它们可以隐藏您存储的数据的实际类型,这可以防止编译器和其他东西检测到类型错误。
在您的特定情况下,使用void *而不是char *没有问题,因此您可以使用void *而无需担心。
编辑:更新并重新编写了纠正错误信息的答案
答案 2 :(得分:0)
当前接受的答案只是为了奉承OP;它实际上有点无效。
考虑您的任意struct
可能(很可能)在某处填充字节。这些填充字节的值是不确定的,可能是也可能不是无关紧要。
如果您put
struct
为key
有填充字节,请考虑会发生什么情况,然后您尝试get
value
{ {1}}除了填充字节外其他方面都是相同的。
如果您将来选择这样做,还要考虑如何处理指针成员。
如果您打算将key
用作struct
,最好将其序列化,这样您就可以保证检索相应的key
而无需担心这些填充位
也许您可以传递一个函数指针,告诉您的包装器如何将value
序列化为字符串......
答案 3 :(得分:0)
你应该序列化为像json这样的标准格式,而不是像这样处理原始数据。它看起来非常容易出错,除非你总是假设任意数据只是一个字节缓冲区。在这种情况下,我将使用uint8_t指针(这是一个unsigned char *)并将所有数据结构转换为它,以便例程只是认为它正在处理字节缓冲区。
关于无效的说明*:我几乎从未使用它们。在介绍void指针时要仔细考虑,因为在大多数情况下,您可以采用正确的方法来利用标准类型来避免将来的错误。你应该使用void *的唯一地方是malloc()这样的地方,那里没有更好的方法。