5.常见优化问题

开发期间可能会遇到这样一种情形:当编译器优化被禁用 (-Ooff) 时,应用程序正常运行,但在使用更高级别的优化 (-O1,-O2,-O3or-O4) 时无法正常运行。造成这种情况的典型原因包括:

  • 从主程序和中断服务例程 (ISR) 访问共享数据
    • Volatile 限定符
    • 原子更新
  • 在不使用 Volatile 的情况下访问存储器映射外设寄存器
  • 使用 C 语言代码调用 asm 函数而不遵循 C 语言规则
  • 未经初始化的变量

5.1.共享数据

  • 由 main() 以及一个或多个 ISR 读取/写入的任何全局变量都必须标注为 Volatile
  • Volatile 会向编译器指明,该变量可能由已知程序流程以外的程序(例如 ISR)修改过
  • 这可确保编译器完全按照 C/C++ 语言代码中的要求保留对全局变量进行 Volatile 读写的次数。编译器不会:
    • 消除冗余读取或写入
    • 重新排序访问

表 2.13 说明了启用优化时需要使用 Volatile。如果不使用 volatile 限定符(在 flag 上),编译器会删除 if 块(在 main() 中),这是因为其分析指明 flag 始终为 0 且 if 条件始终为 false。volatile向编译器指明 main() 以外的程序(本例中为 ISR)可以更新 flag

表 5.1 使用 Volatile
主应用程序 中断服务例程
volatileintflag;intx;intmain(){flag=0;...if(flag==1)x++;...}
externintflag;interruptvoidISR(void){...flag=1;...}

5.2.外设访问

  • 访问代表存储器映射外设的存储器位置时,必须使用 Volatile 关键字。
  • 此类存储器位置可能会以编译器无法预测的方式更改值。
  • 这可确保编译器完全按照 C 语言代码中的要求保留对存储器进行读写的次数。
  • 缺少 Volatile 限定符可能会导致编译器错误地优化掉或重新排序读取/写入。
列表 5.1 使用 Volatile 进行外设寄存器访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
staticinlinevoidGPIO_writePin(uint32_tpin,uint32_toutVal){volatileuint32_t*gpioDataReg;uint32_tpinMask;//// Check the arguments.//ASSERT(GPIO_isPinValid(pin));gpioDataReg=(uint32_t*)GPIODATA_BASE+((pin/32U)*GPIO_DATA_REGS_STEP);pinMask=(uint32_t)1U<<(pin%32U);if(outVal==0U){gpioDataReg[GPIO_GPxCLEAR_INDEX]=pinMask;}else{gpioDataReg[GPIO_GPxSET_INDEX]=pinMask;}}

5.3.原子访问

  • 对最多 32 位全局变量的写入是原子操作(int、float 等)。
  • 对大于 32 位(64 位 long double,结构)全局变量的写入,禁用/重新启用与写入相关的中断。这可确保写入器会在读取器访问整个变量之前更新该变量,并避免使变量处于不一致或不完整的状态。

列表 2.3 是来自 C2000Ware 中的数字控制库的代码片段,说明了禁用与结构更新相关的中断以确保对整个结构进行原子更新。请参阅 TMS320C28x 优化 C/C++ 编译器用户指南中的表 7-6“TMS320C28x C/C++ 编译器内在函数”,详细了解 __enable_interrupt()__disable_interrupt() 内在函数。

列表 5.2 禁用中断以确保原子结构更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
uint16_tval=__disable_interrupts();p->Kp=p->sps->Kp;p->Ki=p->sps->Ki;p->Kd=p->sps->Kd;p->Kr=p->sps->Kr;p->c1=p->sps->c1;p->c2=p->sps->c2;p->Umax=p->sps->Umax;p->Umin=p->sps->Umin;DCL_restoreInts(v);// If interrupts were originally enabled, re-enable themif(0U==(val&0x1))__enable_interrupts();

5.4.从 C 语言代码调用 asm 函数

从 C 语言代码调用任何 asm 函数都必须遵循 C 语言调用和寄存器规则。请参阅 TMS320C28x 优化 C/C++ 编译器用户指南中的第 7.2 节“寄存器规则”、第 7.3 节“函数结构和调用规则”和第 7.5 节“同时使用 C/C++ 与汇编语言”。

任何违反这些规则的行为都可能导致应用程序在 -Ooff 时可以运行,但在更高的优化级别下无法运行。

5.5.未经初始化的变量

  • 使用未经初始化的变量可能会导致未定义的行为
  • 应用程序使用未经初始化变量的行为可能会随着优化级别而改变,从而使调试变得困难
  • 局部变量
    • 使用之前必须在应用程序中显式初始化
  • 全局变量
    • C 语言标准规定,在程序开始运行之前,必须将没有显式初始化的全局 (extern) 和静态变量初始化为 0。
    • C 语言运行时初始化行为在 COFF 和 EABI 中有所不同
    • 有关详细信息,请参阅 TMS320C28x 优化 C/C++ 编译器用户指南 - 第 7.10.3 节“COFF 变量的自动初始化”和第 7.10.4 节“EABI 变量的自动初始化”。