C / C ++优化如何影响volatile变量?

时间:2017-05-10 15:09:16

标签: c gcc optimization

我遇到一些代码问题(见下文)。当所有优化都关闭(-O0)时,代码的行为与我期望的一样,但是当我启用优化时,代码不会像我期望的那样执行。所以我在这里发帖,看看是否有人看到了在优化开启时代码运行方式不同(错误)的原因。让我解释一下代码在做什么......

TPM0-> CNT是一个定时器寄存器,具有易失性uint32_t类型。当TPM0-> CNT达到它的最大值并且翻转中断时产生。在此中断中,sg_CpuCount将增加自上次中断以来经过的定时器周期数。

GetCpuCount函数的作用是返回瞬时计数(即sg_CpuCount + TPM0-> CNT)但是因为中断可能在任何时刻发生并且计时器不会停止在读取/计算时必须小心。读取这两个组件,然后再次测试第一个组件以查看它是否发生了变化。如果它改变了已知的在第一次和第二次读取之间触发的中断并且重复该过程,但是如果它们是相同的,则知道没有发生中断并且返回组件的添加。

逻辑是否合理?优化会产生错误代码吗?任何见解?提前谢谢!

编辑:我应该提一下优化时我注意到的行为。基本上后续调用GetCpuCount并不总是像单调优化时那样单调增加。这是后来对GetCpuCount的调用可能返回的数字比以前的调用少 - 这是不可能的。

edit2:我应该提一下这个代码运行的micro是一个32位架构(我使用的是64位值)。我仍然认为应该没问题,但要考虑一下。

static volatile int64_t sg_CpuCount;

void TPM0_IRQHandler(void)
{
   TPM0->SC |= TPM_SC_TOF(1); //clear interrupt flag
   sg_CpuCount += CPU_CLOCK / TICKRATE_HZ;
}

int64_t GetCpuCount(void)
{
   for (;;) {
      int64_t tmpCpuCount = sg_CpuCount;
      uint32_t tmpCnt = TPM0->CNT;
      if (tmpCpuCount == sg_CpuCount) {
         return tmpCpuCount + tmpCnt;
      }
   }
}

edit3:添加asm

优化(-0s)

GetCpuCount:
.LFB36:
    .loc 2 80 0
    .cfi_startproc
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    push    {r4, r5, r6, lr}
    .cfi_def_cfa_offset 16
    .cfi_offset 4, -16
    .cfi_offset 5, -12
    .cfi_offset 6, -8
    .cfi_offset 14, -4
.LBB2:
    .loc 2 82 0
    ldr r4, .L20
    .loc 2 83 0
    ldr r6, .L20+4
.L18:
    //int64_t tmpCpuCount = sg_CpuCount;
    ldr r0, [r4]
    ldr r1, [r4, #4]
    //uint32_t tmpCnt = TPM0->CNT;
    ldr r5, [r6, #4]
    //if (tmpCpuCount == sg_CpuCount) {
    ldr r2, [r4]
    ldr r3, [r4, #4]
    cmp r0, r2
    bne .L18
    cmp r1, r3
    bne .L18
    //return tmpCpuCount + tmpCnt;
    movs    r0, r5
    movs    r1, #0
.LBE2:
    .loc 2 88 0
    @ sp needed
.LBB3:
    .loc 2 85 0
    adds    r0, r0, r2
    adcs    r1, r1, r3
.LBE3:
    .loc 2 88 0
    pop {r4, r5, r6, pc}
.L21:
    .align  2
.L20:
    .word   sg_CpuCount
    .word   1073971200
    .cfi_endproc

非优化(-00)

GetCpuCount:
.LFB36:
    .loc 2 80 0
    .cfi_startproc
    @ args = 0, pretend = 0, frame = 16
    @ frame_needed = 1, uses_anonymous_args = 0
    push    {r4, r7, lr}
    .cfi_def_cfa_offset 12
    .cfi_offset 4, -12
    .cfi_offset 7, -8
    .cfi_offset 14, -4
    sub sp, sp, #20
    .cfi_def_cfa_offset 32
    add r7, sp, #0
    .cfi_def_cfa_register 7
.L19:
.LBB2:
    //int64_t tmpCpuCount = sg_CpuCount;
    ldr r3, .L21
    ldr r4, [r3, #4]
    ldr r3, [r3]
    str r3, [r7, #8]
    str r4, [r7, #12]
    //uint32_t tmpCnt = TPM0->CNT;
    ldr r3, .L21+4
    ldr r3, [r3, #4]
    str r3, [r7, #4]
    //if (tmpCpuCount == sg_CpuCount) {
    ldr r3, .L21
    ldr r4, [r3, #4]
    ldr r3, [r3]
    ldr r0, [r7, #8]
    cmp r0, r3
    bne .L19
    ldr r0, [r7, #12]
    cmp r0, r4
    bne .L19
    //return tmpCpuCount + tmpCnt;
    ldr r3, [r7, #4]
    movs    r1, r3
    movs    r3, #0
    movs    r2, r3
    ldr r3, [r7, #8]
    ldr r4, [r7, #12]
    adds    r3, r3, r1
    adcs    r4, r4, r2
.LBE2:
    .loc 2 88 0
    movs    r0, r3
    movs    r1, r4
    mov sp, r7
    add sp, sp, #20
    @ sp needed
    pop {r4, r7, pc}
.L22:
    .align  2
.L21:
    .word   sg_CpuCount
    .word   1073971200
    .cfi_endproc

2 个答案:

答案 0 :(得分:1)

如果您使用的是32位机器,则不能保证64位操作是原子的。由于优化,时间可能会发生变化。永远不要在禁用中断的情况下调用该函数。

static volatile uint32_t sg_CpuCount;

void TPM0_IRQHandler(void)
{
   TPM0->SC |= TPM_SC_TOF(1); //clear interrupt flag
   sg_CpuCount++; //count interrupts
}

int64_t GetCpuCount(void)
{
   for (;;) {
      uint32_t tmpCpuCount = sg_CpuCount;
      uint32_t tmpCnt = TPM0->CNT;
      if (tmpCpuCount == sg_CpuCount) {
         return (int64_t)tmpCpuCount * CPU_CLOCK / TICKRATE_HZ + tmpCnt;
      }
   }
} 

答案 1 :(得分:1)

在分析ASM以获得优化和非优化代码后,我发现了一个主要区别。首先加载64位int高字,然后另一个低字首先加载。所以我相信user5329483是正确的,我会解释可能导致问题的确切顺序...

read sg_CpuCount low
read sg_CpuCount high
read TPM0->CNT
read sg_CpuCount low
***interrupt***
read sg_CpuCount high
compare first sg_CpuCount with second sg_CpuCount (equal!!!!!)

由于中断中sg_CpuCount的增量只能改变低位字,因此上述序列无法检测到中断发生。

非优化代码如下:

read sg_CpuCount high
read sg_CpuCount low
read TPM0->CNT
read sg_CpuCount high
***interrupt***
read sg_CpuCount low
compare first sg_CpuCount with second sg_CpuCount (**NOT** equal!!!!!)

此代码正确检测到中断发生。

因此,优化确实改变了代码 - 但是从编译器的角度来看,两个代码gens同样有效。所以答案是通过将64位int分成2个32位变量来明确定义操作的顺序,或者更好地使用user5329483的建议代码。

编辑:遗憾的是,32位不足以运行足够长的时间。无论如何,现在很好地理解了这个问题。