首页 文章

这种未定义行为的基本原理是什么?

提问于
浏览
5

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

为什么这一行是未定义的行为?

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;

是因为编译器不确定易失性元素在访问它们的顺序中可能会有不同的行为吗? (情况是这样的)

但是,由于运营商具有相同的优先级,是否应该确保计算从左到右执行?

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;
    
  • 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;
    

相关问题