这个问题有@ideasman42的评论说:
不确定它是否值得另一个问题,但有兴趣知道你为什么可以写void function (int volatile arg);
我觉得值得问个问题,但是没看到,所以就在这里。如果有的话,这样做的效果是什么?
是什么激发了我,是写一组稍微不同于Raspberry Pi Pico SDK提供的包装器函数。具体来说:
io_rw_32 _i2s_get_hw_clkdiv(struct _i2s* i2s)
{
return i2s->pio_ch->sm[i2s->pio_sm].clkdiv;
}
void _i2s_set_hw_clkdiv(struct _i2s* i2s, io_rw_32 clkdiv)
{
i2s->pio_ch->sm[i2s->pio_sm].clkdiv = clkdiv;
}
埋藏在SDK深处的头文件显示typedef volatile uint32_t io_rw_32;
。
当然可以
uint32_t _i2s_get_hw_clkdiv(struct _i2s* i2s)
{
return i2s->pio_ch->sm[i2s->pio_sm].clkdiv;
}
void _i2s_set_hw_clkdiv(struct _i2s* i2s, uint32_t clkdiv)
{
i2s->pio_ch->sm[i2s->pio_sm].clkdiv = clkdiv;
}
,并且(可能?)生成完全相同的代码,但如果由于某些不可预见的原因typedef
发生了变化,我宁愿尽可能少地进行数据类型转换。
如果有的话,这样做的效果是什么?
它告诉编译器每次使用形参时都必须重新加载它的值(或者,如果源代码修改了形参,则每次都必须写入新值)。
几乎没有理由这样做。对于const
,如果您打算永远不修改函数参数,那么将其声明为const
可能是有用的。如果您声明它为const
,并且由于拼写错误而修改了它,而您打算使用其他名称,编译器将警告您。然而,volatile
通常用于访问特殊硬件,但函数参数由编译器管理,不应该需要volatile
。当您正在调试并希望能够从调试器更改参数时,您可能希望将volatile
与函数参数一起使用。然后使用volatile
将确保生成的代码每次在源代码中使用时都获得新值。因为这只用于调试,所以不应该在已部署的代码中使用。
…生成完全相同的代码…
不,它没有。对于普通的uint32_t
参数,启用了优化的Clang在多次使用参数时不会从内存中重新加载参数。在准备代码中的第二次函数调用时:
void foo0(uint32_t clkdiv)
{
bar(clkdiv);
bar(clkdiv);
}
Clang从寄存器中复制clkdiv
的值。相反,在这段代码中:
void foo1(io_rw_32 clkdiv)
{
bar(clkdiv);
bar(clkdiv);
}
Clang从内存中重新加载参数。有趣的是,它不是传递参数给函数的内存,而是被调用函数的堆栈帧中的内存。参数通过寄存器传递给函数,函数将其存储在内存中以创建参数。(形参是用实参的值初始化的对象;它不是参数本身。)
使用volatile类型作为参数是错误的,因为它会导致低效的代码生成。