26

微控制器通常需要读取寄存器以清除某些状态条件。C 中是否有一种可移植的方式来确保在不使用数据的情况下不会优化读取?将指向内存映射寄存器的指针声明为易失性就足够了吗?换句话说,以下内容是否总是适用于标准兼容的编译器?

void func(void)
{
   volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;

   *REGISTER;
}

我知道处理这样的功能会遇到与编译器相关的问题。所以,在这种情况下,我对便携的定义有点松散。我的意思是它会尽可能广泛地与最流行的工具链一起使用。

4

5 回答 5

10

人们非常激烈地争论到底是什么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是内联代码时。)

于 2012-12-11T16:21:01.820 回答
5

是的,C 标准保证访问 volatile 变量的代码不会被优化掉。

C11 5.1.2.3/2

“访问 volatile 对象,” ...“都是副作用”

C11 5.1.2.3/4

“如果一个实际的实现可以推断出它的值没有被使用并且没有产生任何需要的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。”

C11 5.1.2.3/6

“对符合要求的实施的最低要求是:

— 对 volatile 对象的访问严格按照抽象机的规则进行评估。”

于 2012-12-12T15:11:48.320 回答
2

IIRC,C 标准对使用的定义有点松散,因此*REGISTER不一定将其解释为执行read

但以下应该做:

int x = *REGISTER;

也就是说,内存引用的结果必须在某处使用。然而x,它不需要是易变的。

更新:为了避免 _unused 变量的警告,您可以使用无操作函数。静态和/或内联函数应该在没有运行时损失的情况下被优化掉:

static /*inline*/ void no_op(int x)
{ }

no_op(*REGISTER);

更新2:我刚刚想出了一个更好的功能:

static unsigned int read(volatile unsigned int *addr)
{
    return *addr;
}

read(REGISTER);

现在,这个函数既可以用于读取和使用,也可以用于读取和丢弃。8-)

于 2012-12-11T16:01:51.173 回答
1

也许 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()将处理不同的寄存器大小。即使我不确定使用read2()8 位或 16 位寄存器是否会成为问题,但至少没有关于参数类型的警告。

于 2014-11-05T20:47:49.277 回答
0

编译器通常不会优化汇编内联(很难正确分析它们)。此外,这似乎是一个合适的解决方案:您希望对寄存器进行更明确的控制,这对于汇编来说是很自然的。

由于您正在编写微控制器,我假设您的代码中已经有一些汇编,所以一些内联汇编不会成为问题。

于 2012-12-11T16:22:17.877 回答