这个未定义行为的原理是什么?

5

警告[...]: 未定义行为:在此语句中,volatile访问的顺序是未定义的 x.cpp xxx

为什么这行代码是未定义行为?

  case 2:
    Vdda = 3.3 * (*VREFINT_CAL) / ADC_DR->DATA;

声明/初始化的位置:

volatile short const *VREFINT_CAL = (short *) 0x1FFFF7BA;

并且

volatile STRUCT_ADC_DR *ADC_DR = (STRUCT_ADC_DR*) 0x40012440;

定义者:

typedef struct
{
  unsigned DATA         : 16;
  unsigned              : 16;
} STRUCT_ADC_DR;

是否因为编译器不确定volatile元素在访问顺序上的行为可能会有所不同?(这是什么情况)

但是,应该确保按照从左到右的顺序执行计算,因为运算符具有相同的优先级,是吗?


1
我认为这句话应该重新表述为“标准为什么明确将其标记为未定义行为/背后的原理是什么...”,否则你只会从很多人那里得到“标准就是这样说”的回答。 - OMGtechy
6
操作数的计算顺序未指定。 - molbdnilo
2
@deviantfan:至少在C语言中我这么做,而且我敢打赌C++也是从C语言继承过来的。为什么会有硬编码地址呢?因为它是一个嵌入式系统。 - dhein
1
http://supp.iar.com/Support/?Note=99411 - Cheers and hth. - Alf
2
编译器在发出警告时是正确的,尽管文本并不完全正确:操作数的评估顺序(在这种情况下)是未指定的而不是未定义的。 - edmz
显示剩余11条评论
2个回答

10

volatile表示对编译器来说,你正在读取的不是普通的内存地址,例如I/O端口。如果有两个这样的读取,你很可能希望它们按特定顺序发生。

在C和C++中,操作数的求值顺序没有定义。如果可以帮助你,将除法视为函数调用:

Vdda = 3.3 * divide(*VREFINT_CAL, ADC_DR->DATA);

现在的关键是,对于volatile,如果顺序很重要,您可能不希望将此决策留给编译器。因此,它会发出警告。

为了消除警告,只需通过向代码引入附加的序列点来明确顺序即可。例如:

short const x = *VREFINT_CAL;
unsigned const y = ADC_DR->DATA;
Vdda = 3.3 * x / y;

3
你说得很对。如果在VREFINT_CAL之前访问DATA,会导致数值被重置,这可能会带来大麻烦。 - dhein
@Zaibis 很高兴听到警告实际上帮助您在这种情况下捕获了一个错误。 - ComicSansMS
@Zaibis确实,Alf报告的链接告诉你的确是这样的,当回答“这是个问题吗?”时:这取决于你的数据以及你的应用程序如何处理它。编译器说它可能会引起问题,因为它的工作远非仅限于此。 - edmz
@black 我自己设置了一个0的警告容忍度,所以至少对我的良心来说这是个问题。但在这种情况下,它更是如此。因此感谢您的帮助,也感谢Alf提供的资源。 - dhein

4
为了理解这个问题,你需要知道“求值顺序”和“优先级”的区别。
拿你的表达式举例:
Vdda = 3.3 * (*VREFINT_CAL) / ADC_DR->DATA;

优先级(以及括号)决定了抽象语法树(AST)的构建方式。最终结果将类似于以下内容:

=
  Vdda
  *
    3.3
    /
      *
        VREFINT_CAL
      ->
        ADC_DR
        DATA

评估顺序由序列点的存在决定。而您的代码只有一个序列点,在表达式末尾(;)。

因此,任何子表达式的评估顺序都是未指定的。也就是说,编译器可以按照任意顺序进行任何中间计算和内存访问。有些人喜欢认为子表达式从左到右进行评估,但这不是语言的工作方式。

通常情况下,这不会产生任何影响,但您的两个子表达式都是volatile*VREFINT_CALADC_DR->DATA),因此顺序很重要。也许这对您来说无关紧要,但它肯定对编译器很重要。

为解决问题,请使用一些临时变量,仅添加一个中间序列点:

short a = *VREFINT_CAL;
unsigned b = ADC_DR->DATA;
Vdda = 3.3 * a / b;

@Zaibis:是的,但你仍然需要一个本地变量:short a; a = *VREFINT_CAL, b = ADC_DR->DATA, Vdda = 3.3 * a / ...;。如果你尝试这样做:Vdda = 3.3 * (0,*VREFINT_CAL) / ...; 这是行不通的。 - rodrigo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接