【问题标题】:Is it well defined to use a function pointer that's been cast to another type in a comparison?在比较中使用已转换为另一种类型的函数指针是否定义明确?
【发布时间】:2018-05-15 19:00:06
【问题描述】:

不同类型的函数指针不能直接比较:

#include <iomanip>
#include <iostream>

int foo() { return 0; }
void bar(int) {}

int main()
{
    // Doesn't compile, the comparison is not allowed
    std::cout << std::boolalpha << (&foo == &bar) << std::endl;
    return 0;
}

但是,如果一个函数指针被强制转换为另一个函数指针的类型,是否定义了将该强制转换的结果与另一个函数指针进行比较的行为?

#include <iomanip>
#include <iostream>

int foo() { return 0; }
void bar(int) {}

int main()
{
    auto cast_ptr = reinterpret_cast<decltype(&bar)>(&foo);

    // Printed "false" when I tried it, but is this guaranteed?
    std::cout << std::boolalpha << (cast_ptr == &bar) << std::endl;
}

如果两个运算符都被转换为一个共同但不同的类型呢?

#include <iomanip>
#include <iostream>

int foo() { return 0; }
void bar(int) {}

int main()
{
    using cast_type = void(*)();
    auto cast_foo = reinterpret_cast<cast_type>(&foo);
    auto cast_bar = reinterpret_cast<cast_type>(&bar);

    // Also printed "false" when I tried it, but is this guaranteed?
    std::cout << std::boolalpha << (cast_foo == cast_bar) << std::endl;
}

我知道函数指针比较相等当且仅当它们都指向nullptr 或同一个函数。我不清楚的是,是否允许在比较中使用已转换为另一个函数指针类型的函数指针。

上下文

我正在维护一个带有 c 兼容 API 的 c++ 库。该库记录对 API 函数的每次调用。在运行时有选择地禁用某些功能的日志记录变得很有用。就可用性而言,当前最好的提议是提供一个新的 API 函数,该函数将指向应该禁止记录的 API 函数的指针作为参数。由于 API 函数具有不同的参数,因此这些指针将具有不同的类型,并且需要转换为常见的函数指针类型,例如 void(*)()。然后,在记录 API 函数调用之前,将在 void(*)() 的容器中搜索被调用函数的地址,以了解是否记录该调用。

【问题讨论】:

  • 投射到std::intptr_t 怎么样?
  • @PasserBy 这是一个有趣的想法。 std::intptr_t 可以存储指针,但我不清楚它是否可以存储函数指针。函数指针不需要与常规指针具有相同的大小。
  • 指针可以显式转换为任何大到足以容纳它的整数类型。映射函数是实现定义的。 如果大小足够大,似乎(大多数情况下)它很好。
  • 除了将“指向T1的指针”类型的纯右值转换为“指向T2的指针”类型(其中T1T2是函数类型)并返回对其原始类型产生原始指针值,这种指针转换的结果是未指定的。 似乎转换为不同的函数指针类型并使用它们是不可靠的
  • 而不是依赖于实现定义的行为,你不能只存储类型擦除的包装器和正确定义的 operator== 吗?

标签: c++ casting function-pointers


【解决方案1】:

来自[expr.reinterpret.cast]

函数指针可以显式转换为不同类型的函数指针。 [...] 除了将类型“指向T1”的纯右值转换为“指向T2”的类型(其中T1T2 是函数类型)并返回其原始类型会产生原始指针值,这种指针转换的结果是未指定的。

未指定强制转换的函数指针是否相等。

指针可以显式转换为任何大到足以容纳它的整数类型。映射函数是实现定义的。

因此,转换为 std::intptr_t 或其他合适的类型是很自然的选择,前提是实现不使用一些古怪的转换。

来自GCC's documentation

如果指针表示大于整数类型,则从指针到整数的强制转换丢弃最高有效位,如果指针表示小于整数类型,则符号扩展1,否则位没有改变。

又名理智的转换。在大多数情况下,我敢打赌,这可能是你会发现的。

[1] GCC 的未来版本可能会进行零扩展,或者使用目标定义的 ptr_extend 模式。不要依赖符号扩展。

【讨论】:

  • 正如问题的 cmets 中提到的,我不相信 std::intptr_t 可以存储函数指针,其大小不需要与常规指针相同。对于其他类型的函数指针的整数表示,我也不确定生成的整数表示对于函数指针来说是唯一的。我很感激你的时间和你所做的研究。我会看看是否找不到任何解决这些新问题的方法。
  • @FrançoisAndrieux 这是我从 gcc 的文档中得到的最好的结果。函数指针 AFAIK 上不允许进行其他转换,因此这可能就是您所拥有的。在这种情况下,我可能只是相信实现不会把我搞砸。你知道没有一个健全的实现会有两个不同的函数具有相同的地址,而且由于 gcc 说这些位是不变的,我会说这是对唯一性的保证。
  • 如果 OP 想要保持更多的 C 风格,void * 函数地址转换就足够了。然后为了比较将它们(取决于架构)转换为 uint32_tuint64_t 就足够了,并像这样比较它们的地址?
猜你喜欢
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 2020-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多