STM32 相同的 while 循环代码但编译为不同的汇编代码

STM32 same while loop code but compiled to different assembly code

我正在 stm32F411RE 板 (Cortex-M4) 上学习 RTOS。我使用 MDK uVision v5。我遇到一个C代码的问题while loop。以下代码在我的项目和讲师的项目(在 Udemy 上)中完全相同,但是,在编译两个项目(在我的 PC 上)之后,汇编代码看起来不同。我想问一下这有什么不同。谢谢。

void osSignalWait(int32_t *semaphore)
{
    __disable_irq();
    while(*semaphore <=0)
    {       
            __disable_irq();        
            __enable_irq();
    }
    *semaphore -= 0x01;
    __enable_irq();
}

在调试视图中(见图),如果条件不匹配,则不会加载实际值 LDR r1,[r0, #0x00] 和然后做比较。相反,它比较并去执行 while 循环内的命令。 我的代码编译如下

   100: void osSignalWait(int32_t *semaphore) 
   101: { 
0x08001566 4770      BX            lr
   102:         __disable_irq(); 
   103:         while(*semaphore <=0) 
   104:         {               
0x08001568 B672      CPSID         I
   101: { 
   102:         __disable_irq(); 
   103:         while(*semaphore <=0) 
   104:         {               
0x0800156A 6801      LDR           r1,[r0,#0x00]
0x0800156C E001      B             0x08001572
   105:                         __disable_irq();                 
0x0800156E B672      CPSID         I
   106:                         __enable_irq(); 
   107:         } 
   108:         *semaphore -= 0x01; 
0x08001570 B662      CPSIE         I
0x08001572 2900      CMP           r1,#0x00
0x08001574 DDFB      BLE           0x0800156E
0x08001576 1E49      SUBS          r1,r1,#1
   109:         __enable_irq(); 
0x08001578 6001      STR           r1,[r0,#0x00]
0x0800157A B662      CPSIE         I
   110: } 

如果我编译讲师(在 Udemy 上)的代码(在我的 PC 上使用他的项目),汇编代码看起来会有所不同(具有完全相同的 while 循环代码)。它会再次加载真实值并进行比较。 下面编译的讲师代码(在我的电脑上编译)

100: void osSignalWait(int32_t *semaphore) 
   101: { 
0x08000CDE 4770      BX            lr
   102:         __disable_irq(); 
0x08000CE0 B672      CPSID         I
   103:         while(*semaphore <=0) 
   104:         { 
0x08000CE2 E001      B             0x08000CE8
   105:                         __disable_irq();                         
0x08000CE4 B672      CPSID         I
   106:                         __enable_irq();   
   107:         } 
0x08000CE6 B662      CPSIE         I
0x08000CE8 6801      LDR           r1,[r0,#0x00]
0x08000CEA 2900      CMP           r1,#0x00
0x08000CEC DDFA      BLE           0x08000CE4
   108:         *semaphore -= 0x01; 
0x08000CEE 6801      LDR           r1,[r0,#0x00]
0x08000CF0 1E49      SUBS          r1,r1,#1
0x08000CF2 6001      STR           r1,[r0,#0x00]
   109:         __enable_irq(); 
   110:          
   111:          
0x08000CF4 B662      CPSIE         I
   112: } 

因为你没有告诉编译器 semaphore 可以在这个函数的执行过程中改变,你的编译器已经决定优化你的代码并只加载一次信号量的值,同时使用它的副本循环,最后只写结果。正如现在所写的那样,编译器没有理由认为这可能有害。

要通知编译器一个变量可以在函数外改变,在那个函数执行过程中,请使用volatile关键字,见: https://en.cppreference.com/w/c/language/volatile

在这种情况下,您的代码将变为:

void osSignalWait(volatile int32_t *semaphore)
{
    __disable_irq();
    while(*semaphore <=0)
    {       
        __disable_irq();        // Note: I think the order is wrong...
        __enable_irq();
    }
    *semaphore -= 0x01;
    __enable_irq();
}

顺便说一句,调用 __disable_irq 两次(一次在 while 循环之前,然后在循环开始时)然后 __enable_irq 似乎有点靠不住,你不是说启用(和做某事)然后在 while 循环中禁用?

这是众所周知的 keil 过度优化错误。多次举报。有内存破坏,它应该每次都读取内存。

这里是一个 clobbers 工作的例子

#include <stdint.h>

unsigned x;
volatile unsigned y;


int foo()
{
    while(x < 1000);
}

int bar()
{
    while(x < 1000) asm("":::"memory");
}
foo:
        ldr     r3, .L5
        ldr     r3, [r3]
        cmp     r3, #1000
        bxcs    lr
.L3:
        b       .L3
.L5:
        .word   x
bar:
        ldr     r1, .L11
        ldr     r2, .L11+4
        ldr     r3, [r1]
        cmp     r3, r2
        bxhi    lr
.L9:
        ldr     r3, [r1]
        cmp     r3, r2
        bls     .L9
        bx      lr
.L11:
        .word   x
        .word   999