【问题标题】:Is it safe to convert a template lambda to a `void *`?将模板 lambda 转换为 `void *` 是否安全?
【发布时间】:2017-12-06 18:29:52
【问题描述】:

我正在使用在汇编器中实现的协程来实现纤程。协程由cocall 工作以更改堆栈。

我想使用更高级别的接口在 C++ 中公开它,因为 cocall 程序集只能处理单个 void* 参数。

为了处理模板 lambda,我尝试将它们转换为 void*,发现虽然它可以编译和工作,但我想知道这样做是否安全,假设堆栈的所有权语义 (由纤维保存)。

template <typename FunctionT>
struct Coentry
{
    static void coentry(void * arg)
    {
        // Is this safe?
        FunctionT * function = reinterpret_cast<FunctionT *>(arg);

        (*function)();
    }

    static void invoke(FunctionT function)
    {
        coentry(reinterpret_cast<void *>(&function));
    }
};

template <typename FunctionT>
void coentry(FunctionT function)
{
    Coentry<FunctionT>::invoke(function);
}


int main(int argc, const char * argv[]) {
    auto f = [&]{
        std::cerr << "Hello World!" << std::endl;
    };

    coentry(f);
}

这安全吗?另外,它是否有效?通过转换为void*,我是否会强制编译器选择效率较低的表示?

此外,通过在不同的堆栈上调用coentry(void*),但原始的invoke(FunctionT) 已返回,是否有可能使堆栈无效以恢复? (类似于,我猜是在 std::thread 中调用)。

【问题讨论】:

  • C 是 not allow casting function pointers to void*,我知道它在 C++ 中是一样的——除非最近发生了变化(我不知道),否则无论如何你都在做非法的事情......
  • @VTT 不应该所有指针的大小都相同吗?
  • 不,指针的大小可能会有所不同。在 x86 上,指向常规函数的指针的大小(在您的情况下,lambda 只是一个普通函数,因为它不捕获任何内容)与数据指针的大小相同。但它可能会有所不同。指向成员函数的指针的大小要大得多。同样在这个例子中,你实际上可以传递一个指向 lambda 对象的指针,而不是一个函数。
  • @ioquatix 可能有一些特殊的cpu,其中函数指针和数据指针的大小不同(它与地址、数据和指令的大小有关)总线),但在今天的普通 cpu 上它们具有相同的大小。

标签: c++ function lambda coroutine reinterpret-cast


【解决方案1】:

上面所做的一切都是定义的行为。唯一的性能损失是通过 void 指针内联别名 thro7gh 可能会稍微困难一些。

但是,lambda 是一个实际值,如果存储在自动存储中,则只会持续与存储在堆栈中的帧一样长。

您可以通过多种方式解决此问题。 std::function 是一个,另一个是将 lambda 存储在 shared_ptr&lt;void&gt;unique_ptr&lt;void, void(*)(void*)&gt; 中。如果您不需要类型擦除,您甚至可以将 lambda 存储在具有推导类型的结构中。

前两个很简单。第三个;

template <typename FunctionT>
struct Coentry {
  FunctionT f;
  static void coentry(void * arg)
  {
     auto* self = reinterpret_cast<Coentry*>(arg);

    (self->f)();
  }
  Coentry(FunctionT fin):f(sts::move(fin)){}
};
template<class FunctionT>
Coentry<FunctionT> make_coentry( FunctionT f ){ return {std::move(f)}; }

现在让您的Coentry 保持足够长的时间,直到任务完成。

您如何管理生命周期的细节取决于您问题其余部分的结构。

【讨论】:

  • 这看起来很有趣,我将尝试这种方法。我很欣赏这个代码示例,因为它对准确理解您的意思很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
  • 1970-01-01
  • 2015-03-16
  • 2023-04-03
  • 2011-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多