How to force an unused memory read in C that won&#

2019-02-05 16:41发布

Microcontrollers often require a register to be read to clear certain status conditions. Is there a portable way in C to ensure that a read is not optimized away if the data is not used? Is it sufficient that the pointer to the memory mapped register is declared as volatile? In other words, would the following always work on standard compliant compilers?

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

   *REGISTER;
}

I understand that dealing with functionality like this runs into compiler-dependent issues. So, my definition of portable is a bit loose in this case. I just mean that it would work as widely as possible with the most popular toolchains.

5条回答
老娘就宠你
2楼-- · 2019-02-05 17:05

Perhaps GNU C specific extensions is not considered very portable, but here is another alternative.

#define read1(x)  \
({ \
  __typeof(x) * _addr = (volatile __typeof(x) *) &(x); \
  *_addr; \
})

This will translate to the following assembler line (compiled with gcc x86 and optimized with -O2) : movl SOME_REGISTER(%rip), %eax?

I get the same assembler from:

inline read2(volatile uint32_t *addr) 
{ 
   return *addr; 
}`

... as suggested in another answer, but read1() will handle different register sizes. Even though I'm not sure if usingread2() with 8 or 16-bit registers would ever be an issue, there are at least no warnings on parameter type.

查看更多
趁早两清
3楼-- · 2019-02-05 17:07

People argue quite strenuously about exactly what volatile means. I think most people agree that the construct you show was intended to do what you want, but there is no general agreement that the language in the C standard actually guarantees it as of C99. (The situation may have been improved in C2011; I haven't read that yet.)

A nonstandard, but fairly widely supported by embedded compilers, alternative that may be more likely to work is

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

(The 'volatile' here appies to the 'asm' and means 'this may not be deleted even though it has no output operands. It is not necessary to put it on the pointer as well.)

The major remaining drawback of this construct is that you still have no guarantee that the compiler will generate a one-instruction memory read. With C2011, using _Atomic unsigned int might be sufficient, but in the absence of that feature, you pretty much have to write a real (nonempty) assembly insert yourself if you need that guarantee.

EDIT: Another wrinkle occurred to me this morning. If reading from the memory location has the side-effect of changing the value at that memory location, you need

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

to prevent mis-optimization of other reads from that location. (To be 100% clear, this change will not change the assembly language generated for func itself, but may affect optimization of surrounding code, particularly if func is inlined.)

查看更多
叛逆
4楼-- · 2019-02-05 17:11

Compilers usually do not optimize assembly inlines (it's hard to analyze them properly). Moreover, it seems to be a proper solution: you want more explicit control over the registers and it's natural for assembly.

Since you're programming a microcontroller, I assume that there is some assembly already in your code, so a bit of inline assembly won't be a problem.

查看更多
Anthone
5楼-- · 2019-02-05 17:23

IIRC, the C standard is a bit loose in the definition of use, so the *REGISTER is not necessarily interpreted as doing a read.

But the following should do:

int x = *REGISTER;

That is, the result of the memory reference has to be used somewhere. The x does not need to be volatile, however.

UPDATE: To avoid the warning of _unused variable you could do with a no-op function. A static and/or inline function should be optimized away without runtime penalty:

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

no_op(*REGISTER);

UPDATE 2: I've just came up with a nicer function:

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

read(REGISTER);

Now, this function can be used both for read-and-use and for read-and-discard. 8-)

查看更多
霸刀☆藐视天下
6楼-- · 2019-02-05 17:32

Yes, the C standard guarantees that code accessing a volatile variable will not be optimized away.

C11 5.1.2.3/2

"Accessing a volatile object, " ... "are all side effects"

C11 5.1.2.3/4

"An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object)."

C11 5.1.2.3/6

"The least requirements on a conforming implementation are:

— Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine."

查看更多
登录 后发表回答