((void(*)(void))0)() 是退出函数吗? [复制]

Posted

技术标签:

【中文标题】((void(*)(void))0)() 是退出函数吗? [复制]【英文标题】:((void(*)(void))0)() is Exit function? [duplicate] 【发布时间】:2017-12-19 05:20:40 【问题描述】:

我试图在 Atmel AVR 微控制器上编写自己的引导加载程序。我已经从 github 引用了其中一个代码库。我要感谢ZEVERO的代码库

在初级阶段,我了解代码库。但在第 224 行我发现了一行 Reference to the code

**if (pgm_read_word(0) != 0xFFFF) ((void(*)(void))0)();   //EXIT BOOTLOADER**

我理解 if 条件部分,但是当我试图理解真正的陈述部分时,即

**((void(*)(void))0)();**

代码编写者对此给出了解释是//EXIT BOOTLOADER

我的第一个问题是这个复杂声明的含义是什么 **((void(*)(void))0)();**

第二个问题是,它是否退出了微控制器中代码的执行。

【问题讨论】:

否(不是直接退出代码);它可能会崩溃,或者以其他方式执行地址 0 处的代码(可能是重新启动代码)。这是未定义的行为,因此很糟糕,除非有一个上下文赋予它定义的行为。 对于编写此代码的平台,它可能是重置代码。但这绝不是可移植的。始终使用标准的exit 函数。 【参考方案1】:

正如@iBug 所指出的,((void(*)(void))0)(); 在 NULL 函数指针上调用函数。

实际上,这会将程序控制权转移到内存地址 0。现在,在工作站上,这将是巨大的 UB,很可能会导致段错误。

但是,由于有问题的代码用于硬件引导加载程序,它不是 UB,它(显然)只是退出引导加载程序。

在硬件级别,几乎一切都依赖于实现,几乎没有任何东西是可移植的。您不能期望针对特定硬件平台的 C 代码以任何方式代表普遍接受的 C 模式和实践。

【讨论】:

嗯...好地方。 我不懂 UB? UB -- 未定义行为 它表示标准未涵盖的 C 语言的一个方面,因此编译器实现可以随意处理它。【参考方案2】:

((void(*)(void))0)(); 尝试调用 NULL 函数指针。 AVR 微控制器的用户程序(不是引导加载程序)通常从地址 0 开始执行。AVR-GCC 的 ABI 使用 NULL 函数指针的全 0 位表示,因此此调用将(除其他外)将执行转移到用户程序。本质上,它作为__asm__ __volatile__("jmp 0"); 的较慢版本工作,并假定用户程序的启动代码无论如何都会重新初始化堆栈指针。

通过 NULL 函数指针调用是未定义的行为,因此无法保证此技巧适用于其他编译器、更高版本的 GCC 甚至不同的优化设置。

调用前的if (pgm_read_word(0) != 0xFFFF)检查可能是为了确定是否存在用户程序:已擦除但未写入的程序存储器字将读取为0xFFFF,而大多数程序以JMP指令开始跳过中断向量表的其余部分,并且JMP 指令的第一个字永远不会是 0xFFFF。

【讨论】:

【参考方案3】:

如前所述,调用此函数只会导致跳转到地址 0。

由于该地址处的代码通常不是由您自己的程序定义的,而是由特定环境定义的,因此行为完全取决于该环境。

您的问题被标记为 AVM/Atmel:在 AVR 上,跳转到地址 0 只会导致重新启动(与硬件重置几乎相同的行为,但请注意,MCU 将保持中断启用/禁用状态,而不是“真实”重置)。 “更清洁”的程序可能希望使用看门狗定时器来进行“真正的”重置(wdt_reset() 等人)。

【讨论】:

【参考方案4】:

它将简单地调用地址 0,就好像它是一个返回 void 并且不带任何参数的函数。或者......更简单的地址是空指针的位模式。或者更不简单的是,行为是未定义的,所以它可能会做任何意想不到的事情。

【讨论】:

以上是关于((void(*)(void))0)() 是退出函数吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

戒除void main()陋习

验证函子的目标对象

C经典案例

如何表示来自 emscripten/webassembly 调用的 void* 返回

简述static关键字void与void *(void指针)函数指针

多线程,线程的退出顺序