如何强制在C中使用未被优化的未使用内存?

时间:2012-12-11 15:58:14

标签: c embedded microcontroller volatile cpu-registers

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

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

   *REGISTER;
}

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

5 个答案:

答案 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)

IIRC,C标准在使用定义上有点松散,因此*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()是否会出现问题,但参数类型至少没有警告。