【问题标题】:Function that takes arbitrary data: Use void* or char*?接受任意数据的函数:使用 void* 还是 char*?
【发布时间】:2015-06-24 09:54:16
【问题描述】:

我最近在 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* 将任意数据传递给函数?

【问题讨论】:

标签: c


【解决方案1】:

是否有理由不使用 void* 将任意数据传递给 功能?

没有。事实上,void * 存在是为了方便传递任意数据而不需要丑陋的强制转换。这就是 ptr-to-void 被标准化的原因。至少在 C 中。 C++ 是另一种野兽。

在 LevelDB,他们必须处理由 char * 或 C89 之前的编译器生成的历史代码,或任何其他导致重构惯性的隐蔽原因。他们的代码也可以与 ptrs-to-void 一起使用。

请注意,在您的 db_put 版本中,应该删除强制转换,因为它们是多余的。

【讨论】:

    【解决方案2】:

    当前接受的答案只是为了奉承OP;它实际上有点无效。

    考虑您的任意struct 可能(很可能)在某处有填充字节。这些填充字节的值是不确定的,可能无关紧要。

    考虑如果您将putstruct 作为具有填充字节的key 会发生什么情况,然后您尝试将get value 用于该key,除了填充之外,它在其他方面是相等的字节。

    如果您将来选择这样做,请考虑如何处理指针成员。

    如果您打算将struct 用作key,最好将其序列化,这样您就可以保证检索到相应的value,而不必担心那些填充位。

    也许您可以传递一个函数指针,告诉您的包装器如何将key 序列化为字符串...

    【讨论】:

    • 我的错;您提出的问题似乎非常不同是否有理由不使用void* 将任意数据传递给函数?)...这不是一个约束违反,并且(int 想法,not struct 想法)可能大部分时间都有效。关于它的可怕之处(这是我的回答的要点)是,当它无法工作时,它会默默地这样做......你被警告了; int 也可能有填充。
    【解决方案3】:

    void* 可以被认为是一个包含指针的黑盒子。通过持有它 在一个 void* 中,您实际上是在说您不在乎或它包含在那一点上,因此这将允许您对其做出任何假设。 Void* 是一个“真正的”通用指针,可以直接分配给任何特定的数据类型,而无需使用强制转换。

    同时,一个 char* 明确指定了相应对象的类型。最初没有 char* 并且 char* 也用于表示通用指针。当使用 char* 时,需要显式转换,但是不建议使用 char* 作为通用指针,因为它可能会造成混淆,就像它在很难判断 char* 是否包含字符串或一些通用数据。

    此外,在 char* 上执行算术运算是合法的,但在 void* 上则不行。

    使用 void* 的缺点在于它们的主要用途,它们可以隐藏您存储的数据的实际类型,从而阻止编译器和其他东西检测到类型错误。

    在您的具体情况下,使用 void* 代替 char* 没有问题,因此您可以放心使用 void*。

    编辑:更新并重新制定答案以纠正一些错误信息

    【讨论】:

    • 您可以将 any 指针类型转换为任何其他指针类型:6.3.2.3 指针:“指向 void 的指针可以转换为指向任何指针的指针或从指向任何指针的指针转换为对象类型。指向任何对象类型的指针可以转换为指向 void 的指针,然后再转换回来;结果应该与原始指针相等。" 意味着您可以将任何指针强制转换为void *,因为@ 987654322@,可以再次转换为任何 ptr 类型。指针算术确实不合法,取消引用 void * 也是如此。 IMO,后者就是为什么 void *char * 在功能上根本不等效
    • @EliasVanOotegem 你说的有问题:函数指针不是指向任何对象类型的指针。因此,您关于 any 指针类型可以转换为 any other 指针类型的建议在 source target 转换的类型是函数指针。
    • @EliasVanOotegem “J.5”部分的标题是什么?
    • @undefinedbehaviour: "J.5 Common extensions" 哎呀……这就是你想要太聪明的结果。你是对的,我会删除评论,也许第二次添加相同的信息,这次指定将 void * 转换为函数指针是不可移植的
    • 勘误:对象指针可以安全地从void * 转换到void *。但是,不能保证函数指针就是这种情况,能够将对象指针转换为函数指针在标准中列为通用扩展,但依赖它可能会导致可移植性问题(参考 J.5.7 函数指针转换,在 J.5 通用扩展下)
    【解决方案4】:

    您应该序列化为像 json 这样的标准格式,而不是像这样处理原始数据。除非您总是假设任意数据只是一个字节缓冲区,否则它看起来很容易出错。在这种情况下,我将使用 uint8_t 指针(它是一个无符号字符 *)并将所有数据结构转换为它,以便例程认为它正在处理一个字节缓冲区。

    关于 void* 的说明:我几乎从不使用它们。引入 void 指针时要仔细考虑,因为在大多数情况下,您可以摒弃正确的做事方式,利用标准类型来避免未来的错误。您应该使用 void* 的唯一地方是像 malloc() 这样没有更好方法的地方。

    【讨论】:

      猜你喜欢
      • 2017-05-23
      • 1970-01-01
      • 2013-01-13
      • 1970-01-01
      • 2011-04-04
      • 2021-11-25
      • 1970-01-01
      • 2015-08-14
      • 2019-05-27
      相关资源
      最近更新 更多