首页 > 解决方案 > 这个函数铸造安全吗?

问题描述

这是标准的 Arduino 库。在第 92 行https://github.com/arduino/ArduinoCore-megaavr/blob/master/cores/arduino/WInterrupts.cpp用户提供的函数void(*)(void)被转换为void(*)(void *) (voidFuncPtrParam is void(*)(void *))

这是如何运作的?在第 138 行,始终使用参数调用用户提供的函数void *,无论它是void(*)(void)还是void(*)(void *)。你甚至可以安全地做到这一点吗?

标签: c++arduino

解决方案


这个函数铸造安全吗?

演员表本身总是安全的。您始终可以将函数指针转换为任何函数类型。

WInterrupts.cpp#L138)处的调用确实是未定义的行为,因为它通过指针调用void (void)函数。void (void*)

这是如何运作的?

该代码是专门为 megaavr 编写的,可以在使用 megaavr 编译器编译的 megaavr 上运行。由于参数是如何传递给函数的,所以附加参数只是在调用方的寄存器中设置,而在被调用方则被忽略。

你甚至可以安全地做到这一点吗?

安全代码会做一个蹦床,可能会牺牲性能。

static void trampoline_call_user_func(void *param) {
    // let's be super super safe and use memcpy instead of cast to void*
    void (*userFunc)(void);
    memcpy(&userFunc, &param, sizeof(fptr));
    userFunc();
}

void attachInterrupt(uint8_t pin, void (*userFunc)(void), PinStatus mode) {
   // let's be super super safe and use memcpy instead of cast to void*
   void *param;
   static_assert(sizeof(void*) >= sizeof(userFunc), "");
   memcpy(&param, &userFunc, sizeof(userFunc));

   attachInterruptParam(pin, trampoline_call_user_func, param, NULL);
}

但是在任何现代架构上,只需通过void*“将起作用”传递函数指针(尽管函数指针不需要能够转换为void*),只是:

static void trampoline_call_user_func(void *param) {
    // let's be super super safe and use memcpy instead of cast to void*
    void (*userFunc)(void) = param;
    userFunc();
}

void attachInterrupt(uint8_t pin, void (*userFunc)(void), PinStatus mode) {
   attachInterruptParam(pin, trampoline_call_user_func, userFunc, NULL);
}

推荐阅读