C语言 volatile参数的函数?



这个问题有@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类型作为参数是错误的,因为它会导致低效的代码生成。

最新更新