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

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

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

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

人们非常激烈地争论volatile意味着什么。 我想大多数人都同意你展示的结构是为了做你想要的,但是没有普遍认为C标准中的语言实际上保证它是C99的。 (情况可能在C2011有所改善;我还没有读过。)

嵌入式编译器非标准但相当广泛的支持,可能更有可能的替代方案是

 void func(void) { asm volatile ("" : : "r" (*(unsigned int *)0x12345678)); } 

(’volatile’在这里应用于’asm’并且意味着’即使它没有输出操作数,也可能不会被删除。也没有必要将它放在指针上。)

此构造的主要缺点是您仍然无法保证编译器将生成单指令内存读取。 使用C2011,使用_Atomic unsigned int 可能就足够了,但是如果没有该function,如果需要保证,则必须自己编写一个真实(非空)程序集插件。

编辑:今天早上发生了另一个皱纹。 如果从内存位置读取具有更改该内存位置值的副作用,则需要

 void func(void) { unsigned int *ptr = (unsigned int *)0x12345678; asm volatile ("" : "=m" (*ptr) : "r" (*ptr)); } 

防止来自该位置的其他读取的错误优化。 (要100%清楚,此更改不会更改为func本身生成的汇编语言,但可能会影响周围代码的优化,尤其是在内联func情况下。)

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

C11 5.1.2.3/2

“访问易失性对象,”……“都是副作用”

C11 5.1.2.3/4

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

C11 5.1.2.3/6

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

– 严格根据抽象机器的规则评估对易失性对象的访问。“

IIRC,C标准在使用定义上有点松散,因此*REGISTER不一定被解释为执行读取

但以下应该做:

 int x = *REGISTER; 

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

更新 :为避免_unused变量的警告,你可以使用no-op函数。 应该优化静态和/或内联函数,而不会影响运行时间:

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

更新2 :我刚刚提出了一个更好的function:

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

现在,此function既可用于读取和使用,也可用于读取和丢弃。 8-)

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

由于您正在编程一个微控制器,我认为您的代码中已经有一些汇编,因此一些内联汇编不会成为问题。

也许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()是否会出现问题,但参数类型至少没有警告。