【问题标题】:Why pass a pointer as a (char *) and cast to a (long *)为什么将指针作为 (char *) 传递并转换为 (long *)
【发布时间】:2018-07-07 00:59:33
【问题描述】:

我知道遗产总是一个理由,但我想看看这个来自MariaDB and see if I understand it enough to critique what's going on,的例子

static int show_open_tables(THD *, SHOW_VAR *var, char *buff) {
  var->type = SHOW_LONG;
  var->value = buff;
  *((long *)buff) = (long)table_cache_manager.cached_tables();
  return 0;
}

他们在这里接收char*,并将其写入var->value which is also a char*。然后他们在buff 中强制一个指向long 的指针并将类型设置为SHOW_LONG 以表明它。

我想知道为什么他们会为此使用 char* 而不是 uintptr_t - 特别是当他们强制指向 long 和其他类型的指针时。

uintptr_t 之前的规范不是在 C++ 中使用 void* 进行多态性吗?

【问题讨论】:

  • 您需要研究show_open_tables() 是如何在更广泛的代码库中实际使用的。我看到它被存储在一个查找表中,其中包含使用除long 之外的其他数据类型的其他函数。很明显,char* 参数的使用是要求函数具有统一签名的较大 API 的一部分。在这种情况下,使用void* 而不是char* 可能更有意义
  • 是否有任何理由将指针存储为char* 而不是void*,这对于uintptr_t 来说是一个理想的情况,C++11 是 "MySQL 8.0源代码允许使用 C++11 功能。"?
  • 文件包含最低级别的字节。最接近的 C++ 类型是 char。记忆也是如此。
  • 强制转换为long* 可能是因为缓冲区在开始时具有已知的内存布局,其中包含long 值。
  • 干得好,这是一个静态的。我不希望任何人在公共场合看到我的脏床单。

标签: c++ mysql pointers polymorphism void-pointers


【解决方案1】:

这里似乎有两个问题。所以我把我的答案分开了。

使用char*

使用char* 很好。 C 和 C++ 标准对字符类型(charsigned charunsigned char)进行了特殊处理。 C 标准定义了以下访问对象的规则:

对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型,
  • 与对象的有效类型兼容的类型的限定版本,
  • 对应于对象有效类型的有符号或无符号类型,
  • 一种有符号或无符号类型,对应于对象有效类型的限定版本,
  • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的成员),或
  • 一种字符类型。

这实际上意味着字符类型是最接近定义“字节”类型的标准(C++17 中的std::byte 刚刚定义为enum class byte : unsigned char {}

但是,根据上述规则,将 char* 转换为 long* 然后分配给它是不正确的(尽管通常在实践中有效)。应该改用memcpy。例如:

long cached_tables = table_cache_manager.cached_tables();
memcpy(buf, &cached_tables, sizeof(cached_tables));

void* 也是一个合理的选择。是否更好是见仁见智的问题。我想说最明确的选择是为char 添加一个类型别名,以传达将其用作字节类型的意图(例如typedef char byte_t)。尽管我能想到几个使用 char 原样作为字节类型的著名库的例子,但在我的脑海中。例如,Boost 内存映射文件代码给出了char*,leveldb 使用std::string 作为字节缓冲区类型(大概是为了利用 SSO)。

关于uinptr_t

uintptr_t 是一个可选 类型,定义为能够保存指针的无符号整数。如果要将指向对象的地址存储在整数中,那么它是一种合适的类型。它不适合在这里使用。

【讨论】:

  • 这一切都很好,但我严重怀疑该代码的作者是否知道或关心指针别名问题。那时它根本不相关。
  • @PaulSanders:作者可能假设适合系统编程的高质量编译器能够处理该构造——恕我直言,今天和以往一样正确的假设。唯一改变的是编译器编写者现在将实现质量问题视为产生糟糕但“符合”编译器的邀请。
  • @supercat 什么系统编程?
  • @PaulSanders:看看代码 sn-p,我认为它适用于适合系统编程的编译器(其中包括管理重用的能力)用于不同目的的存储),而不是例如那些专门用于运行不需要任何“有趣”指针语义并且从不回收堆存储的高端数字处理应用程序,除非通过malloc 和朋友[而不是分配一个大块并保留它,同时使用其中的字节用于多种用途]。
  • @supercat 它是 MySQL 的一部分,上帝帮助我们。
【解决方案2】:

他们正在接受 char* 并将其写入 var->value,它也是一个 char*。然后他们强制一个指针指向缓冲区中的 long 并将类型设置为 SHOW_LONG 以表明它。

什么的。那个代码太可怕了。

我想知道为什么他们会为此使用 char* 而不是 uintptr_t ——尤其是当他们强制指针指向其中的 long 和其他类型时。

谁知道?谁知道这个人在写的时候在做什么?谁在乎?那段代码太可怕了,我们当然不应该试图从中学习。

在 C++ 中使用 void* 进行多态性的规范不是 pre-uintptr_t 吗?

是的,现在仍然如此。 uintptr_t 的目的是定义一个大到足以容纳指针的整数类型。

我想看看 MariaDB 中的这个例子,看看我是否理解它足以批评发生了什么

您可能对此有所保留,但我当然不会,该 API 只是一个公然的谎言。这样做的方法(如果你绝对必须)将(显然)是:

static int show_open_tables(THD *, SHOW_VAR *var, long *buff) {

  var->type = SHOW_LONG;
  var->value =  (char *) buff;
  *buff = (long)table_cache_manager.cached_tables();
  return 0;

}

那么至少它不再是一颗定时炸弹了。


嗯,好吧,也许(只是也许)该函数在某个地方的调度表中使用,因此需要(除非你强制转换它)具有特定的签名。如果是这样,我当然不会去挖掘 10,000 行代码来找出答案(无论如何,我做不到,它太长了,我的平板电脑都崩溃了)。

但如果有的话,那只会让事情变得更糟。现在那个定时炸弹已经变成了stealth bomber。无论如何,我暂时不相信是这样。这只是一个危险的废话。

【讨论】:

  • char*long* 是否更好取决于容器的性质。如果代码专门用于适合低级编程并在允许未对齐存储的平台上运行的高质量实现,并且如果有问题的指针是通过从池中分配任意数量的字节形成的,char* 不会不合理虽然我更喜欢uint8_t。在允许它的质量实现上,我倾向于转换为__packed long*,但这不是标准功能。
  • @supercat 我不知道你在说什么,抱歉。
  • @supercat 但我上面的评论可能会对事情有所启发。
  • 代码的目的很可能是在buff给出的地址处写入四个连续的字节。基本上等同于temp = ...; buff[0] = temp; buff[1] = temp >> 8; buff[2] = temp >> 16 buff[3]= temp >> 24;,但在大多数支持它的实现上要快得多。
  • @supercat 当然可以。您可以看到代码正是这样做的。 那么为什么要告诉调用者传入一个 char *`?就像我说的,定时炸弹。作者一定是度过了糟糕的一天。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-12
  • 2017-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
  • 1970-01-01
相关资源
最近更新 更多