在C中,如果B是易失性的,那么表达式(void)(B = 1)应该读取B.

时间:2011-02-28 10:31:56

标签: c variable-assignment c99 volatile

我在几个嵌入式平台的编译器上工作。用户最近抱怨我们的一个编译器出现以下行为。给出这样的代码:

extern volatile int MY_REGISTER;

void Test(void)
{
    (void) (MY_REGISTER = 1);
}

编译器生成它(在伪汇编程序中):

Test:
    move regA, 1
    store regA, MY_REGISTER
    load regB, MY_REGISER

也就是说,它不仅写入MY_REGISTER,而且之后将其读回。由于性能原因,额外负载使他心烦意乱。我解释说这是因为根据标准“赋值表达式在赋值后具有左操作数的值,[...]”

奇怪的是,删除cast-to-void会改变行为:负载消失。用户很高兴,但我只是感到困惑。

所以我也在几个版本的GCC(3.3和4.4)中检查了这一点。在那里,编译器永远不会生成负载,即使明确使用了该值,例如

int TestTwo(void)
{
    return (MY_REGISTER = 1);
}

变成

TestTwo:
    move regA, 1
    store regA, MY_REGISTER
    move returnValue, 1
    return

有没有人对该标准的正确解释有何看法?回读是否应该发生?如果使用该值或将其转换为void,则添加只读是正确还是有用?

3 个答案:

答案 0 :(得分:7)

标准中的相关段落是

  

赋值运算符存储值   在左边指定的对象中   操作数。赋值表达式有   之后左操作数的值   赋值,但不是左值。   赋值表达式的类型   是除左边操作数的类型   左操作数具有限定类型,   在这种情况下,它是不合格的   左侧类型的版本   操作数。更新左操作数的存储值的副作用应为   发生在前一个和下一个序列点之间。

因此,这明显区分了“左操作数的值”和存储值的更新。另请注意,返回值不是左值(因此在表达式的返回中没有对变量的引用)并且所有限定符都将丢失。

所以我读到这是因为当gcc返回它知道必须存储的值时,正在做正确的事。

修改

即将推出的标准计划通过添加脚注来澄清:

  

允许实施   读取对象以确定值   但不是必须的,即使是   object具有volatile限定类型。

编辑2:

实际上还有一个关于表达式陈述的段落可能会对此有所了解:

  

表达式中的表达式   声明被评估为无效   表达其副作用。\ footnote {例如赋值和具有副作用的函数调用}

由于这意味着对于这样的语句不需要返回值的效果,这强烈暗示如果使用该值,则只能从变量加载该值。

总而言之,当客户看到变量已加载时,他的确非常不满。如果你扩展它的解释,这种行为可能符合标准,但它显然是在可接受的边界线上。

答案 1 :(得分:6)

回读似乎更接近标准(特别是考虑到读取一个volatile变量可能导致与写入的值不同的值),但我很确定它不是大多数使用volatile的代码所期望的特别是在读取或写入volatile变量触发其他一些影响的情况下。

volatile一般来说定义不明确 - “什么构成了对象的访问权限 volatile的限定类型是实现定义的。“

编辑:如果我必须编写一个编译器,我想我不会读回变量,如果没有使用它并重新读取它,如果是,但带有警告。那么应该使用无效的演员吗?

(void) v;

肯定应该是一个,考虑到这一点,我没有任何理由

(void) v = exp;

不是。但无论如何,我会发出警告,解释如何获得其他效果。

顺便说一句,如果您在编译器上工作,您可能有人与C委员会联系,填写正式的缺陷报告将为您带来一个具有约束力的解释(好吧,存在DR被归类为“非缺陷”的风险) “没有暗示他们想要什么......”

答案 2 :(得分:1)

标准中的语言没有说明读取volatile变量,只是赋值表达式的值是什么,a)是由C语义定义的,而不是由变量的内容定义的,b)这里不使用,所以无需计算。