微控制器通常需要读取寄存器以清除某些状态条件。在C中是否有可移植的方式来确保在不使用数据的情况下不优化读取?指向内存映射寄存器的指针是否足以声明为volatile?换句话说,以下内容是否始终适用于标准兼容编译器?
void func(void)
{
volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;
*REGISTER;
}
我知道处理这样的功能会遇到编译器相关的问题。所以,在这种情况下,我对便携式设备的定义有点松散。我只是说它会尽可能广泛地使用最流行的工具链。
答案 0 :(得分:9)
人们非常强烈地争论volatile
的含义。我想大多数人都同意你展示的构造意图做你想做的事情,但是没有普遍认同C标准中的语言实际上保证它 C99。 (情况可能在C2011有所改善;我还没有读过。)
非标准但受嵌入式编译器广泛支持的替代方案可能更有可能
void func(void)
{
asm volatile ("" : : "r" (*(unsigned int *)0x12345678));
}
('volatile'在这里应用于'asm'并且意味着'即使它没有输出操作数,也可能不会被删除。也没有必要将它放在指针上。)
此构造的主要缺点是您仍然无法保证编译器将生成单指令内存读取。使用C2011,使用_Atomic unsigned int
可能就足够了,但是如果没有该功能,如果需要保证,则必须自己编写一个真实的(非空)程序集插件。
编辑:今天早上发生了另一次皱纹。如果从内存位置读取具有更改该内存位置值的副作用,则需要
void func(void)
{
unsigned int *ptr = (unsigned int *)0x12345678;
asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
}
防止来自该位置的其他读取的错误优化。 (要100%明确,此更改不会更改为func
本身生成的汇编语言,但可能会影响周围代码的优化,尤其是在内联func
的情况下。)
答案 1 :(得分:4)
是的,C标准保证访问volatile变量的代码不会被优化掉。
C11 5.1.2.3/2
“访问易失性对象,”......“都是副作用”
C11 5.1.2.3/4
“实际实现不需要评估表达式的一部分,如果它可以推导出它的那个 不使用value并且不会产生任何副作用(包括通过调用函数或访问volatile对象而导致的任何副作用)。“
C11 5.1.2.3/6
“符合实施的最低要求是:
- 严格按照抽象机的规则评估对易失性对象的访问。“
答案 2 :(得分:2)
*REGISTER
不一定被解释为执行读取。
但以下情况应该如下:
int x = *REGISTER;
也就是说,必须在某处使用内存引用的结果。但是,x
不需要是易变的。
UPDATE :为了避免使用_unused函数的警告,你可以使用no-op函数。应该优化静态和/或内联函数,而不会影响运行时间:
static /*inline*/ void no_op(int x)
{ }
no_op(*REGISTER);
更新2 :我刚刚想出了一个更好的功能:
static unsigned int read(volatile unsigned int *addr)
{
return *addr;
}
read(REGISTER);
现在,此功能既可用于读取也可用于读取和丢弃。 8 - )
答案 3 :(得分:0)
编译器通常不优化汇编内联(很难正确分析它们)。此外,它似乎是一个合适的解决方案:您希望更明确地控制寄存器,这对于汇编来说很自然。
由于您正在编程一个微控制器,我认为您的代码中已经有一些汇编,因此一些内联汇编不会成为问题。
答案 4 :(得分:0)
也许GNU C特定扩展可能不太便于携带, 但这是另一种选择。
#define read1(x) \
({ \
__typeof(x) * _addr = (volatile __typeof(x) *) &(x); \
*_addr; \
})
这将转换为以下汇编程序行(使用gcc x86编译并使用-O2优化):
movl SOME_REGISTER(%rip), %eax?
我得到了相同的汇编程序:
inline read2(volatile uint32_t *addr)
{
return *addr;
}`
...如另一个答案所示,但read1()
将处理不同的寄存器大小。
即使我不确定使用带有8位或16位寄存器的read2()
是否会出现问题,但参数类型至少没有警告。