【问题标题】:Is calling a function pointer to generated code undefined behavior?是否调用函数指针来生成代码未定义的行为?
【发布时间】:2021-02-14 03:43:56
【问题描述】:

有没有办法在纯标准 C++ 中以某种方式调用在缓冲区中生成的函数?

假设:

  1. 我们使用了特定于实现的知识(以及特定于架构的知识)来生成字节序列,以在 char 的数组中实现某些功能。
  2. 此实现与实现的调用约定和 ABI 的其他相关部分正确匹配,因此如果作为 C++ 函数调用,它实际上会正常运行
  3. 缓冲区被正确标记为可执行文件

也就是说,所有架构/ABI 障碍都已克服,我们需要做的只是调用这段代码。 问题是:是否有一种符合标准的方法来创建指向 this 的函数指针并调用所述函数指针而不会遇到未定义的行为?

据我了解,如果我们可以将指向对象类型的指针转​​换为函数指针类型,则它是实现定义的。我还认为,如果所述指针将指向与原始指针相同的逻辑地址,则它是实现定义的。障碍是调用:从实现的角度来看,我所做的只是尝试将指向字符数组的指针转换为函数指针,然后调用它。如果说 ptr 是对象类型之一,我知道这将违反严格的别名规则,并且是未定义的行为。但是如果我尝试调用这个函数指针,它是未定义的行为还是仅仅是实现定义的?

【问题讨论】:

  • C++ 不支持 ABI,因此它是 UB。虽然如果你能找到在这种情况下指定行为的特定实现的文档,我相信它可以被认为是实现定义的。
  • POSIX 兼容系统上的 C/C++ 实现需要支持调用从 void* 转换的函数指针,以便 dlsym 工作。此外,工作草案中的措辞是有条件地支持对对象类型的这种转换,并且仅当从错误类型的函数转换为 fptr 时,才定义对 fptr 的调用。当/如果它指向不是函数的东西时,没有这样的语言。
  • 从 C++ 标准的角度来看,这是未定义的行为。从您的 ABI 规范或主机操作系统 API 规范(例如 POSIX)的角度来看,它可能是完美定义的行为。从其他一些 ABI 规范(或其他一些主机操作系统的规范)的角度来看,它可能是未定义的行为。
  • C++ 未指定。语言规范说它是 UB。但是,无论如何,几乎 100% 的现实世界软件都在这样做并且工作正常,因为 GetProcAddress( windows ) 和 dlsym ( Linux ) 都返回要转换为可调用函数指针的 void* 值。
  • 回复:“我们使用了特定于实现的知识”——根据定义,这意味着行为是未定义的。未定义的行为仅仅意味着语言定义没有告诉你你的程序做了什么。这并不意味着坏事会发生。

标签: c++ language-lawyer


【解决方案1】:

据我了解您的问题,您询问的代码如下:

char buffer[200] = "some valid operations";
void (*func)() = reinterpret_cast<void(*)()>(buffer);
func();

我在C++ expr.call 7.6.1.3 Function call的标准中看到:

  1. 函数调用是后缀表达式,后跟括号,其中包含可能为空的逗号分隔的初始化子句列表,这些子句构成函数的参数。
    [...]
    后缀表达式应具有函数类型或函数指针类型。 对于对非成员函数或静态成员函数的调用,后缀表达式应为引用函数 [...] 的左值,或具有函数指针类型。

上述代码中的func 是一个左值,它引用char 数组而不是函数。标准中的以下几点描述了函数调用表达式的其他情况,例如虚函数调用或对某个对象的析构函数的调用。这些点也不适用于这里。总而言之,标准没有定义当函数调用表达式应用于引用char数组对象的左值时会发生什么。因为它没有定义,所以它是未定义的行为。

是否有一种符合标准的方法来创建指向 this 的函数指针

只需通过reinterpret_cast 投射即可。结果值是实现定义的。

并在没有遇到未定义行为的情况下调用所述函数指针?

没有。

是否调用了指向生成代码未定义行为的函数指针?

是的。

在纯标准 C++ 中,有没有办法以某种方式调用在缓冲区中生成的函数?

没有。

但是如果我尝试调用这个函数指针,它是未定义的行为还是仅仅是实现定义的?

未定义的行为。

【讨论】:

  • “在翻译或程序执行期间以有记录的环境特征的方式表现”是允许未定义行为的“允许行为范围”之一,所以看起来这甚至可能是允许的在标准的语言范围内。所以“未定义的行为”,但在我关心的实现上是一致的(并且隐式支持的)。我想我误解了“未定义”的意思是“优化器会践踏它。”
  • “未定义行为”并不意味着不允许这种行为。这仅意味着此标准 强加no requirements on behavior on such programs。其他标准可能会指定根据 C++ 标准未定义的代码行为。 suppose I misunderstood "undefined" to mean "the optimizer will trample over this." 这正是这个意思。如果没有标准记录该行为,则允许编译器做它想做的事情。
【解决方案2】:

它是实现定义的what the conversion means,因此实现可以选择将其解释为指向“由实现定义”的某个函数的指针(好像它的标准库包含所有可能的函数) .我不确定实现是否真的文档支持这种技术(根据实现定义的行为的要求),但将其解释为文档中的错误是合理的。

【讨论】:

    猜你喜欢
    • 2018-08-19
    • 2012-03-18
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 2020-09-30
    • 2014-06-10
    • 2019-06-02
    相关资源
    最近更新 更多