编译器优化(Compiler Optimization)¶
学习目标¶
完成本模块后,你将能够: - 理解编译器优化的基本原理和技术 - 掌握GCC/Clang优化级别(-O0, -O1, -O2, -O3, -Os, -Og)的区别和应用 - 了解常见的编译器优化技术(内联、循环展开、死代码消除等) - 在医疗器械软件中平衡性能、代码大小和可调试性 - 使用编译器特定的优化选项和属性 - 理解优化对代码行为的影响和潜在风险 - 编写编译器友好的代码以获得更好的优化效果 - 分析和验证优化后的代码
前置知识¶
- C/C++编程基础
- 汇编语言基础知识
- 编译、链接过程
- 嵌入式系统架构(ARM Cortex-M)
- 调试工具使用(GDB)
- 性能分析基础
内容¶
概念介绍¶
编译器优化是指编译器在将源代码转换为机器代码时,通过各种技术改进程序的性能、代码大小或功耗,同时保持程序的语义不变。
优化的目标: - 性能优化:提高执行速度,减少CPU周期 - 代码大小优化:减少Flash占用,降低成本 - 功耗优化:减少能耗,延长电池寿命 - 可调试性:保持代码可调试性(开发阶段)
医疗器械软件的特殊考虑: - 安全性第一:优化不能改变程序行为 - 可验证性:优化后的代码必须可测试和验证 - 确定性:实时系统需要可预测的执行时间 - 监管要求:符合IEC 62304软件生命周期标准 - 可追溯性:优化设置必须文档化
编译器优化级别¶
GCC/Clang优化级别¶
# -O0:无优化(默认)
gcc -O0 -o program program.c
# -O1:基本优化
gcc -O1 -o program program.c
# -O2:推荐的优化级别
gcc -O2 -o program program.c
# -O3:激进优化
gcc -O3 -o program program.c
# -Os:优化代码大小
gcc -Os -o program program.c
# -Og:优化调试体验
gcc -Og -o program program.c
# -Ofast:最快速度(可能违反标准)
gcc -Ofast -o program program.c
各优化级别详解¶
-O0(无优化):
// 源代码
int calculate(int a, int b) {
int temp = a + b;
int result = temp * 2;
return result;
}
// -O0 生成的汇编(ARM Cortex-M)
calculate:
push {r7, lr}
sub sp, sp, #16
str r0, [sp, #12] // 保存参数a
str r1, [sp, #8] // 保存参数b
ldr r2, [sp, #12] // 加载a
ldr r3, [sp, #8] // 加载b
add r3, r2, r3 // temp = a + b
str r3, [sp, #4] // 保存temp
ldr r3, [sp, #4] // 加载temp
lsl r3, r3, #1 // result = temp * 2
str r3, [sp, #0] // 保存result
ldr r0, [sp, #0] // 加载result到返回寄存器
add sp, sp, #16
pop {r7, pc}
特点: - 不进行任何优化 - 代码与源代码一一对应 - 最佳调试体验 - 代码大小最大,速度最慢 - 所有变量都在栈上 - 适用于开发和调试阶段
-O1(基本优化):
// 同样的源代码
int calculate(int a, int b) {
int temp = a + b;
int result = temp * 2;
return result;
}
// -O1 生成的汇编
calculate:
add r0, r0, r1 // temp = a + b
lsl r0, r0, #1 // result = temp * 2
bx lr // 返回
启用的优化: - 寄存器分配优化 - 死代码消除 - 常量折叠 - 简单的内联 - 跳转优化
特点: - 编译速度快 - 代码大小减少约30-50% - 性能提升约2-3倍 - 仍然较好的调试体验 - 适用于开发后期
-O2(推荐优化):
启用的优化(在-O1基础上): - 循环优化 - 指令调度 - 公共子表达式消除 - 强度削减 - 更激进的内联 - 别名分析
特点: - 最常用的发布版本优化级别 - 性能提升约3-5倍 - 代码大小减少约40-60% - 编译时间适中 - 调试较困难(变量被优化掉) - 医疗器械软件推荐使用
-O3(激进优化):
额外启用的优化(在-O2基础上): - 循环展开 - 向量化(SIMD) - 函数克隆 - 预测分支 - 更激进的内联
特点: - 最高性能(通常) - 代码大小可能增加(循环展开) - 编译时间长 - 可能引入不确定性 - 医疗器械软件需谨慎使用
-Os(代码大小优化):
优化策略: - 基于-O2,但禁用增加代码大小的优化 - 禁用循环展开 - 禁用函数内联(除非很小) - 启用代码共享
特点: - 最小的代码大小 - 性能略低于-O2 - 适用于Flash受限的设备 - 医疗器械中常用(成本考虑)
-Og(调试优化):
优化策略: - 基于-O1,但保留调试信息 - 不优化掉变量 - 保持代码结构
特点: - 平衡性能和可调试性 - 适用于开发阶段 - GCC 4.8+支持
优化级别对比¶
// 测试代码
#include <stdint.h>
uint32_t sum_array(const uint32_t* arr, uint32_t size) {
uint32_t sum = 0;
for (uint32_t i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
// 性能和代码大小对比(ARM Cortex-M4)
// 数组大小:1000个元素
| 优化级别 | 代码大小 | 执行时间 | 编译时间 | 可调试性 |
|---|---|---|---|---|
| -O0 | 156字节 | 12000周期 | 0.5秒 | 优秀 |
| -O1 | 48字节 | 4500周期 | 0.6秒 | 良好 |
| -O2 | 52字节 | 3200周期 | 1.2秒 | 一般 |
| -O3 | 84字节 | 2800周期 | 2.5秒 | 困难 |
| -Os | 44字节 | 3800周期 | 1.0秒 | 一般 |
| -Og | 64字节 | 5500周期 | 0.7秒 | 良好 |
常见编译器优化技术¶
1. 常量折叠(Constant Folding)¶
编译器在编译时计算常量表达式。
// 源代码
int calculate_buffer_size(void) {
return 1024 * 4 + 256;
}
// -O0:运行时计算
calculate_buffer_size:
mov r0, #1024
lsl r0, r0, #2 // r0 = 1024 * 4
add r0, r0, #256 // r0 = r0 + 256
bx lr
// -O1及以上:编译时计算
calculate_buffer_size:
mov r0, #4352 // 直接返回结果
bx lr
应用:
// 推荐:使用常量表达式
#define BUFFER_SIZE (1024 * 4 + 256)
// 编译器会在编译时计算
uint8_t buffer[BUFFER_SIZE];
// 避免:运行时计算
uint8_t buffer[calculate_buffer_size()]; // 可能不被优化
2. 死代码消除(Dead Code Elimination)¶
移除永远不会执行或结果不被使用的代码。
// 源代码
int process_data(int x) {
int unused = x * 2; // 未使用的变量
int result = x + 10;
if (0) { // 永远为假的条件
result = x * 100;
}
return result;
}
// -O1及以上优化后
int process_data(int x) {
return x + 10; // 死代码被移除
}
// 生成的汇编
process_data:
add r0, r0, #10
bx lr
实际应用:
// 条件编译中的死代码
#define DEBUG_MODE 0
void log_message(const char* msg) {
if (DEBUG_MODE) {
// 这段代码在发布版本中会被完全移除
printf("Debug: %s\n", msg);
}
}
// -O1及以上,DEBUG_MODE=0时
// 函数体为空,可能被完全内联或移除
3. 函数内联(Function Inlining)¶
将函数调用替换为函数体,消除调用开销。
// 源代码
static inline int square(int x) {
return x * x;
}
int calculate(int a, int b) {
return square(a) + square(b);
}
// -O0:函数调用
calculate:
push {r4, r5, lr}
mov r4, r0
mov r5, r1
mov r0, r4
bl square // 调用square(a)
mov r4, r0
mov r0, r5
bl square // 调用square(b)
add r0, r4, r0
pop {r4, r5, pc}
// -O2:内联后
calculate:
mul r0, r0, r0 // a * a
mla r0, r1, r1, r0 // r0 = b*b + r0
bx lr
内联控制:
// 强制内联
static inline __attribute__((always_inline))
int fast_abs(int x) {
return (x < 0) ? -x : x;
}
// 禁止内联
__attribute__((noinline))
void critical_function(void) {
// 必须保持为独立函数(调试、性能分析)
}
// 内联建议(编译器可能忽略)
inline int helper_function(int x) {
return x * 2 + 1;
}
内联的权衡: - 优点:消除函数调用开销,允许进一步优化 - 缺点:增加代码大小,可能降低指令缓存效率 - 建议:小函数(<10行)适合内联,大函数不适合
4. 循环优化¶
循环展开(Loop Unrolling):
// 源代码
void clear_buffer(uint32_t* buf, uint32_t size) {
for (uint32_t i = 0; i < size; i++) {
buf[i] = 0;
}
}
// -O2:部分展开(展开因子4)
void clear_buffer(uint32_t* buf, uint32_t size) {
uint32_t i;
// 主循环:每次处理4个元素
for (i = 0; i < size / 4 * 4; i += 4) {
buf[i] = 0;
buf[i+1] = 0;
buf[i+2] = 0;
buf[i+3] = 0;
}
// 剩余元素
for (; i < size; i++) {
buf[i] = 0;
}
}
// 生成的汇编(简化)
clear_buffer:
// ... 设置 ...
.L_loop:
str r2, [r0], #4 // buf[i] = 0, i++
str r2, [r0], #4 // buf[i+1] = 0, i++
str r2, [r0], #4 // buf[i+2] = 0, i++
str r2, [r0], #4 // buf[i+3] = 0, i++
cmp r0, r1
bne .L_loop
// ... 剩余元素处理 ...
循环不变量外提(Loop Invariant Code Motion):
// 源代码
void scale_array(int* arr, int size, int factor) {
for (int i = 0; i < size; i++) {
arr[i] = arr[i] * (factor + 10); // (factor + 10)是循环不变量
}
}
// 优化后
void scale_array(int* arr, int size, int factor) {
int scale = factor + 10; // 外提到循环外
for (int i = 0; i < size; i++) {
arr[i] = arr[i] * scale;
}
}
循环融合(Loop Fusion):
// 源代码
void process_arrays(int* a, int* b, int size) {
for (int i = 0; i < size; i++) {
a[i] = a[i] * 2;
}
for (int i = 0; i < size; i++) {
b[i] = b[i] + 10;
}
}
// 优化后(融合循环)
void process_arrays(int* a, int* b, int size) {
for (int i = 0; i < size; i++) {
a[i] = a[i] * 2;
b[i] = b[i] + 10;
}
}
优点: - 减少循环开销(计数器更新、条件判断) - 提高指令级并行性 - 更好的缓存局部性
控制循环展开:
// 禁用循环展开
#pragma GCC optimize("no-unroll-loops")
void sensitive_loop(void) {
// 需要精确控制的循环
}
// 指定展开因子
#pragma GCC unroll 8
for (int i = 0; i < 64; i++) {
// 展开8次
}
5. 强度削减(Strength Reduction)¶
用更快的操作替换慢的操作。
// 源代码
int calculate_offset(int index) {
return index * 4; // 乘法
}
// 优化后
int calculate_offset(int index) {
return index << 2; // 左移(更快)
}
// 汇编
calculate_offset:
lsl r0, r0, #2 // 左移2位
bx lr
常见的强度削减:
// 乘以2的幂 → 左移
x * 4 → x << 2
x * 8 → x << 3
// 除以2的幂 → 右移(无符号)
x / 4 → x >> 2
x / 8 → x >> 3
// 取模2的幂 → 位与
x % 4 → x & 3
x % 8 → x & 7
// 乘以常量 → 移位和加法
x * 5 → (x << 2) + x
x * 9 → (x << 3) + x
x * 10 → (x << 3) + (x << 1)
循环中的强度削减:
// 源代码
for (int i = 0; i < 100; i++) {
array[i * 4] = 0; // 每次迭代都乘法
}
// 优化后
int offset = 0;
for (int i = 0; i < 100; i++) {
array[offset] = 0;
offset += 4; // 加法替代乘法
}
6. 公共子表达式消除(Common Subexpression Elimination, CSE)¶
识别并消除重复计算的表达式。
// 源代码
int calculate(int a, int b, int c) {
int x = a * b + c;
int y = a * b - c; // a*b 重复计算
return x + y;
}
// 优化后
int calculate(int a, int b, int c) {
int temp = a * b; // 只计算一次
int x = temp + c;
int y = temp - c;
return x + y;
}
// 进一步优化
int calculate(int a, int b, int c) {
int temp = a * b;
return temp + c + temp - c; // = 2 * temp
}
// 最终优化
int calculate(int a, int b, int c) {
return 2 * (a * b);
}
7. 尾调用优化(Tail Call Optimization)¶
将尾递归转换为循环。
// 源代码:递归
uint32_t factorial(uint32_t n, uint32_t acc) {
if (n == 0) {
return acc;
}
return factorial(n - 1, n * acc); // 尾调用
}
// -O2优化后:转换为循环
uint32_t factorial(uint32_t n, uint32_t acc) {
while (n != 0) {
acc = n * acc;
n = n - 1;
}
return acc;
}
// 汇编(无函数调用开销)
factorial:
cmp r0, #0
beq .L_return
.L_loop:
mul r1, r0, r1
subs r0, r0, #1
bne .L_loop
.L_return:
mov r0, r1
bx lr
8. 向量化(Vectorization)¶
使用SIMD指令并行处理多个数据。
// 源代码
void add_arrays(float* a, float* b, float* c, int size) {
for (int i = 0; i < size; i++) {
c[i] = a[i] + b[i];
}
}
// -O3 -mfpu=neon(ARM NEON)
// 每次处理4个float(128位)
void add_arrays(float* a, float* b, float* c, int size) {
int i;
for (i = 0; i < size / 4 * 4; i += 4) {
// 使用NEON指令
float32x4_t va = vld1q_f32(&a[i]);
float32x4_t vb = vld1q_f32(&b[i]);
float32x4_t vc = vaddq_f32(va, vb);
vst1q_f32(&c[i], vc);
}
// 剩余元素
for (; i < size; i++) {
c[i] = a[i] + b[i];
}
}
启用向量化:
# ARM NEON
gcc -O3 -mfpu=neon -ftree-vectorize
# 查看向量化报告
gcc -O3 -ftree-vectorize -fopt-info-vec-all
医疗器械软件中的优化策略¶
安全关键代码的优化¶
// 安全关键函数:禁用优化
__attribute__((optimize("O0")))
void safety_check(void) {
// 必须按顺序执行,不能被优化
disable_interrupts();
check_watchdog();
verify_memory();
enable_interrupts();
}
// 或使用pragma
#pragma GCC push_options
#pragma GCC optimize ("O0")
void critical_timing_function(void) {
// 需要精确时序的代码
}
#pragma GCC pop_options
// 正常代码:使用优化
void normal_processing(void) {
// 可以安全优化的代码
}
volatile关键字的正确使用¶
// 硬件寄存器:必须使用volatile
#define ADC_DATA_REG (*(volatile uint32_t*)0x40012040)
uint32_t read_adc(void) {
// 编译器不会优化掉重复读取
uint32_t sample1 = ADC_DATA_REG;
uint32_t sample2 = ADC_DATA_REG;
return (sample1 + sample2) / 2;
}
// 错误:没有volatile
#define ADC_DATA_REG_WRONG (*(uint32_t*)0x40012040)
uint32_t read_adc_wrong(void) {
// 编译器可能优化为只读取一次
uint32_t sample1 = ADC_DATA_REG_WRONG;
uint32_t sample2 = ADC_DATA_REG_WRONG; // 可能被优化掉
return (sample1 + sample2) / 2;
}
// 中断标志:使用volatile
volatile bool data_ready = false;
void wait_for_data(void) {
// 没有volatile,循环可能被优化为死循环
while (!data_ready) {
// 等待
}
}
// 中断服务程序
void ADC_IRQHandler(void) {
data_ready = true;
}
内存屏障和原子操作¶
// 内存屏障:防止编译器重排序
#define COMPILER_BARRIER() __asm__ volatile("" ::: "memory")
void critical_sequence(void) {
write_register_1();
COMPILER_BARRIER(); // 确保顺序
write_register_2();
}
// 原子操作:防止优化破坏原子性
#include <stdatomic.h>
atomic_int counter = 0;
void increment_counter(void) {
// 原子递增,不会被优化破坏
atomic_fetch_add(&counter, 1);
}
// 错误:非原子操作
int counter_wrong = 0;
void increment_counter_wrong(void) {
// 可能被优化为非原子操作
counter_wrong++; // 读-改-写,可能被中断
}
链接时优化(LTO)¶
# 启用LTO
gcc -flto -O2 -o program file1.c file2.c file3.c
# 分步编译
gcc -flto -O2 -c file1.c -o file1.o
gcc -flto -O2 -c file2.c -o file2.o
gcc -flto -O2 -c file3.c -o file3.o
gcc -flto -O2 -o program file1.o file2.o file3.o
LTO的优势: - 跨文件内联 - 全局死代码消除 - 更好的寄存器分配 - 代码大小减少10-20%
LTO的风险: - 编译时间大幅增加 - 内存占用高 - 可能暴露跨文件的未定义行为
// file1.c
int global_var = 0;
void set_value(int val) {
global_var = val;
}
// file2.c
extern int global_var;
int get_value(void) {
return global_var;
}
// 使用LTO,编译器可以看到全局视图
// 可能内联set_value和get_value
// 可能优化掉global_var(如果只在这两个函数中使用)
编译器特定的优化选项¶
GCC/ARM特定选项¶
# 目标CPU
-mcpu=cortex-m4 # 指定CPU型号
-mthumb # 使用Thumb指令集
-mfloat-abi=hard # 硬件浮点ABI
-mfpu=fpv4-sp-d16 # FPU类型
# 代码生成
-ffunction-sections # 每个函数独立section
-fdata-sections # 每个数据独立section
-Wl,--gc-sections # 链接时移除未使用section
# 性能优化
-funroll-loops # 循环展开
-finline-functions # 函数内联
-ffast-math # 快速数学(不严格遵守IEEE 754)
# 代码大小优化
-fno-inline # 禁用内联
-fno-unroll-loops # 禁用循环展开
-fshort-enums # 使用最小的enum大小
示例Makefile:
# 编译器
CC = arm-none-eabi-gcc
# 通用标志
CFLAGS = -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
# 调试版本
CFLAGS_DEBUG = $(CFLAGS) -Og -g3 -DDEBUG
# 发布版本(性能优先)
CFLAGS_RELEASE = $(CFLAGS) -O2 -DNDEBUG
# 发布版本(代码大小优先)
CFLAGS_RELEASE_SIZE = $(CFLAGS) -Os -DNDEBUG
# 链接标志
LDFLAGS = -Wl,--gc-sections -Wl,-Map=output.map
# 编译
debug:
$(CC) $(CFLAGS_DEBUG) -o program.elf main.c
release:
$(CC) $(CFLAGS_RELEASE) $(LDFLAGS) -o program.elf main.c
release_size:
$(CC) $(CFLAGS_RELEASE_SIZE) $(LDFLAGS) -o program.elf main.c
函数和变量属性¶
// 对齐
__attribute__((aligned(32)))
uint8_t dma_buffer[1024]; // 32字节对齐(DMA要求)
// 放置在特定section
__attribute__((section(".fast_code")))
void time_critical_function(void) {
// 放在RAM中执行(更快)
}
__attribute__((section(".slow_code")))
void rarely_used_function(void) {
// 放在Flash中(节省RAM)
}
// 弱符号(可被覆盖)
__attribute__((weak))
void default_handler(void) {
// 默认实现
}
// 别名
void real_function(void) {
// 实际实现
}
void alias_function(void) __attribute__((alias("real_function")));
// 构造函数和析构函数
__attribute__((constructor))
void init_before_main(void) {
// 在main之前执行
}
__attribute__((destructor))
void cleanup_after_main(void) {
// 在main之后执行
}
// 纯函数(无副作用)
__attribute__((pure))
int calculate_checksum(const uint8_t* data, size_t len) {
// 相同输入总是返回相同输出
// 不修改全局状态
}
// 常量函数(更严格的纯函数)
__attribute__((const))
int square(int x) {
// 只依赖参数,不访问内存
return x * x;
}
// 热点函数(优化为更快)
__attribute__((hot))
void frequently_called(void) {
// 经常调用的函数
}
// 冷函数(优化为更小)
__attribute__((cold))
void error_handler(void) {
// 很少调用的函数
}
// 不返回
__attribute__((noreturn))
void fatal_error(void) {
while(1);
}
// 格式检查
__attribute__((format(printf, 1, 2)))
void debug_printf(const char* fmt, ...) {
// 编译器检查printf格式
}
编写编译器友好的代码¶
1. 使用const和restrict¶
// const:告诉编译器数据不会被修改
uint32_t sum_array(const uint32_t* arr, uint32_t size) {
uint32_t sum = 0;
for (uint32_t i = 0; i < size; i++) {
sum += arr[i]; // 编译器知道arr[i]不会改变
}
return sum;
}
// restrict:告诉编译器指针不会别名
void copy_array(uint32_t* restrict dst,
const uint32_t* restrict src,
uint32_t size) {
// 编译器知道dst和src不重叠,可以更激进地优化
for (uint32_t i = 0; i < size; i++) {
dst[i] = src[i];
}
}
// 没有restrict的版本
void copy_array_slow(uint32_t* dst,
const uint32_t* src,
uint32_t size) {
// 编译器必须假设dst和src可能重叠
// 生成更保守的代码
for (uint32_t i = 0; i < size; i++) {
dst[i] = src[i];
}
}
2. 避免别名问题¶
// 问题:指针别名
void update_values(int* a, int* b, int* c) {
*a = *b + *c;
*b = *a + *c; // 编译器必须重新加载*a(可能与b或c别名)
}
// 解决方案1:使用restrict
void update_values_fast(int* restrict a,
int* restrict b,
int* restrict c) {
*a = *b + *c;
*b = *a + *c; // 编译器可以使用寄存器中的*a
}
// 解决方案2:使用局部变量
void update_values_local(int* a, int* b, int* c) {
int temp_a = *b + *c;
int temp_b = temp_a + *c;
*a = temp_a;
*b = temp_b;
}
3. 循环优化技巧¶
// 技巧1:循环计数方向
// 慢:向上计数
for (int i = 0; i < size; i++) {
process(data[i]);
}
// 快:向下计数(某些架构)
for (int i = size - 1; i >= 0; i--) {
process(data[i]);
}
// 技巧2:循环展开提示
#pragma GCC unroll 4
for (int i = 0; i < 64; i++) {
buffer[i] = 0;
}
// 技巧3:避免循环中的函数调用
// 慢
for (int i = 0; i < size; i++) {
result += expensive_function(data[i]);
}
// 快:提取不变量
int factor = get_factor();
for (int i = 0; i < size; i++) {
result += data[i] * factor;
}
// 技巧4:使用指针而不是数组索引
// 慢
for (int i = 0; i < size; i++) {
sum += array[i];
}
// 快
uint32_t* ptr = array;
uint32_t* end = array + size;
while (ptr < end) {
sum += *ptr++;
}
4. 分支预测友好的代码¶
// 使用likely/unlikely宏
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// 示例:错误处理
int process_data(uint8_t* data, size_t len) {
if (unlikely(data == NULL)) {
return -1; // 不太可能发生
}
if (likely(len > 0)) {
// 正常路径
for (size_t i = 0; i < len; i++) {
data[i] = process_byte(data[i]);
}
}
return 0;
}
// 避免不可预测的分支
// 慢:分支
int abs_slow(int x) {
if (x < 0) {
return -x;
} else {
return x;
}
}
// 快:无分支
int abs_fast(int x) {
int mask = x >> 31; // 全0或全1
return (x + mask) ^ mask;
}
5. 数据对齐¶
// 对齐结构体以提高访问速度
typedef struct {
uint8_t flag; // 1字节
// 3字节填充
uint32_t value; // 4字节对齐
uint16_t count; // 2字节
// 2字节填充
} __attribute__((aligned(4))) AlignedStruct_t;
// 手动对齐
__attribute__((aligned(32)))
uint8_t cache_line_buffer[64]; // 缓存行对齐
// DMA缓冲区对齐
__attribute__((aligned(4)))
uint8_t dma_buffer[1024];
优化验证和分析¶
查看生成的汇编代码¶
# 生成汇编文件
gcc -S -O2 -o output.s source.c
# 生成带源代码的汇编
gcc -S -O2 -fverbose-asm -o output.s source.c
# 反汇编目标文件
arm-none-eabi-objdump -d -S program.elf > program.asm
# 查看特定函数的汇编
arm-none-eabi-objdump -d program.elf | grep -A 20 "function_name"
分析示例:
// source.c
int sum_array(const int* arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
; -O2优化后的汇编
sum_array:
cmp r1, #0 ; 检查size
ble .L4 ; size <= 0,返回0
mov r3, #0 ; sum = 0
mov r2, #0 ; i = 0
.L3:
ldr ip, [r0, r2, lsl #2] ; 加载arr[i]
add r2, r2, #1 ; i++
add r3, r3, ip ; sum += arr[i]
cmp r1, r2 ; i < size?
bne .L3 ; 继续循环
mov r0, r3 ; 返回sum
bx lr
.L4:
mov r0, #0 ; 返回0
bx lr
代码大小分析¶
# 查看各section大小
arm-none-eabi-size program.elf
# 输出示例:
# text data bss dec hex filename
# 12345 256 1024 13625 353d program.elf
# 详细的section信息
arm-none-eabi-size -A program.elf
# 查看符号大小
arm-none-eabi-nm --size-sort -S program.elf
# 生成map文件(链接时)
gcc -Wl,-Map=output.map -o program.elf source.c
# 分析map文件
grep "\.text\." output.map | sort -k2 -n
优化代码大小:
# Makefile示例
CFLAGS += -Os # 优化代码大小
CFLAGS += -ffunction-sections # 每个函数独立section
CFLAGS += -fdata-sections # 每个数据独立section
LDFLAGS += -Wl,--gc-sections # 移除未使用section
LDFLAGS += -Wl,--print-gc-sections # 打印移除的section
# 进一步优化
CFLAGS += -fno-inline # 禁用内联(减小代码)
CFLAGS += -fno-unroll-loops # 禁用循环展开
CFLAGS += -flto # 链接时优化
性能分析¶
使用GCC性能计数器:
#include <time.h>
// 测量执行时间
void benchmark_function(void) {
clock_t start = clock();
// 执行要测试的代码
for (int i = 0; i < 1000000; i++) {
test_function();
}
clock_t end = clock();
double cpu_time = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Time: %f seconds\n", cpu_time);
}
使用ARM DWT(Data Watchpoint and Trace):
// ARM Cortex-M DWT寄存器
#define DWT_CTRL (*(volatile uint32_t*)0xE0001000)
#define DWT_CYCCNT (*(volatile uint32_t*)0xE0001004)
// 启用DWT
void dwt_init(void) {
DWT_CTRL |= 1; // 启用CYCCNT
}
// 测量周期数
uint32_t measure_cycles(void) {
uint32_t start = DWT_CYCCNT;
// 执行要测试的代码
test_function();
uint32_t end = DWT_CYCCNT;
return end - start;
}
编译器优化报告:
# GCC优化报告
gcc -O2 -fopt-info-all -o program source.c
# 向量化报告
gcc -O3 -ftree-vectorize -fopt-info-vec-all source.c
# 内联报告
gcc -O2 -fopt-info-inline source.c
# 循环优化报告
gcc -O2 -fopt-info-loop source.c
调试优化后的代码¶
# 生成调试信息(即使优化)
gcc -O2 -g -o program source.c
# 使用GDB调试
gdb program
# GDB命令
(gdb) break main
(gdb) run
(gdb) disassemble # 查看汇编
(gdb) info registers # 查看寄存器
(gdb) print variable # 打印变量(可能被优化掉)
调试优化代码的技巧:
// 防止变量被优化掉
volatile int debug_var = 0;
void debug_function(void) {
int important_value = calculate();
// 强制编译器保留变量
debug_var = important_value;
// 或使用内联汇编
__asm__ volatile("" : : "r"(important_value) : "memory");
}
// 禁用特定函数的优化
__attribute__((optimize("O0")))
void debug_this_function(void) {
// 这个函数不会被优化,便于调试
}
优化的潜在风险¶
1. 未定义行为(Undefined Behavior)¶
// 问题:有符号整数溢出
int multiply_unsafe(int a, int b) {
return a * b; // 溢出是未定义行为
}
// 编译器可能假设不会溢出,生成错误的代码
// 例如:优化掉溢出检查
if (a * b / b != a) {
// 溢出检查,但可能被优化掉
}
// 解决方案:使用无符号或检查溢出
bool multiply_safe(int a, int b, int* result) {
if (a > 0 && b > 0 && a > INT_MAX / b) {
return false; // 溢出
}
if (a < 0 && b < 0 && a < INT_MAX / b) {
return false; // 溢出
}
if (a > 0 && b < 0 && b < INT_MIN / a) {
return false; // 溢出
}
if (a < 0 && b > 0 && a < INT_MIN / b) {
return false; // 溢出
}
*result = a * b;
return true;
}
2. 严格别名规则(Strict Aliasing)¶
// 问题:违反严格别名规则
uint32_t read_as_uint32(float f) {
return *(uint32_t*)&f; // 未定义行为!
}
// 编译器可能生成错误的代码
// -fstrict-aliasing(-O2默认启用)
// 解决方案1:使用union
typedef union {
float f;
uint32_t u;
} FloatUint_t;
uint32_t read_as_uint32_safe(float f) {
FloatUint_t converter;
converter.f = f;
return converter.u;
}
// 解决方案2:使用memcpy
uint32_t read_as_uint32_memcpy(float f) {
uint32_t result;
memcpy(&result, &f, sizeof(result));
return result;
}
// 解决方案3:禁用严格别名
// gcc -fno-strict-aliasing
3. 浮点数优化¶
// 问题:-ffast-math可能改变结果
float calculate(float a, float b, float c) {
return (a + b) + c; // 可能被重排为 a + (b + c)
}
// 浮点数加法不满足结合律
// (1e20 + 1.0) - 1e20 = 0.0
// 1e20 + (1.0 - 1e20) = 0.0(不同的结果)
// 医疗器械软件:避免使用-ffast-math
// 使用 -fno-fast-math 或不指定
4. 时序敏感代码¶
// 问题:延迟循环被优化掉
void delay_ms(uint32_t ms) {
for (uint32_t i = 0; i < ms * 1000; i++) {
// 空循环,可能被完全优化掉
}
}
// 解决方案1:使用volatile
void delay_ms_volatile(uint32_t ms) {
volatile uint32_t count = ms * 1000;
while (count--) {
// volatile防止优化
}
}
// 解决方案2:使用内联汇编
void delay_cycles(uint32_t cycles) {
__asm__ volatile(
"1: subs %0, %0, #1 \n"
" bne 1b \n"
: "+r" (cycles)
);
}
// 解决方案3:使用硬件定时器
void delay_ms_timer(uint32_t ms) {
// 使用SysTick或其他定时器
}
最佳实践¶
1. 优化级别选择¶
开发阶段:
- 最佳调试体验 - 快速编译 - 变量可见测试阶段:
- 与发布版本相同的优化 - 保留调试信息 - 发现优化相关的bug发布阶段:
- 医疗器械推荐-O2或-Os - 避免-O3(可能引入不确定性) - 避免-Ofast(违反标准)2. 分层优化策略¶
// 文件级优化控制
#pragma GCC optimize ("O2")
// 安全关键代码:无优化
#pragma GCC push_options
#pragma GCC optimize ("O0")
void safety_critical_function(void) {
// 必须精确执行的代码
}
#pragma GCC pop_options
// 性能关键代码:激进优化
__attribute__((optimize("O3")))
void performance_critical_function(void) {
// 可以安全优化的热点代码
}
// 正常代码:使用默认优化
void normal_function(void) {
// 使用编译选项指定的优化级别
}
3. 优化验证流程¶
# 1. 编译不同优化级别
make clean
make debug # -O0
make release # -O2
# 2. 运行相同的测试
./run_tests debug
./run_tests release
# 3. 比较结果
diff debug_output.txt release_output.txt
# 4. 性能测试
./benchmark debug
./benchmark release
# 5. 代码大小比较
size debug.elf
size release.elf
# 6. 静态分析
cppcheck --enable=all source.c
4. 文档化优化设置¶
/**
* @file signal_processing.c
* @brief 信号处理模块
*
* 优化设置:
* - 编译器:GCC 10.3.1
* - 优化级别:-O2
* - 特殊标志:-ffast-math(已验证不影响精度)
* - 关键函数:fft_process使用-O3优化
*
* 验证:
* - 单元测试:100%通过
* - 性能测试:满足实时要求(<10ms)
* - 精度测试:误差<0.1%
*
* 最后验证:2026-02-09
*/
5. 持续监控¶
// 性能监控
typedef struct {
uint32_t min_cycles;
uint32_t max_cycles;
uint32_t avg_cycles;
uint32_t call_count;
} Performance_Stats_t;
Performance_Stats_t stats = {0};
void monitored_function(void) {
uint32_t start = DWT_CYCCNT;
// 实际功能
actual_function();
uint32_t cycles = DWT_CYCCNT - start;
// 更新统计
if (cycles < stats.min_cycles || stats.call_count == 0) {
stats.min_cycles = cycles;
}
if (cycles > stats.max_cycles) {
stats.max_cycles = cycles;
}
stats.avg_cycles = (stats.avg_cycles * stats.call_count + cycles) /
(stats.call_count + 1);
stats.call_count++;
// 检查性能退化
if (cycles > PERFORMANCE_THRESHOLD) {
log_warning("Performance degradation detected");
}
}
6. 编译器警告¶
# 启用所有警告
CFLAGS += -Wall -Wextra -Wpedantic
# 优化相关警告
CFLAGS += -Wuninitialized # 未初始化变量
CFLAGS += -Wmaybe-uninitialized
CFLAGS += -Wstrict-aliasing # 别名问题
CFLAGS += -Wfloat-equal # 浮点数比较
CFLAGS += -Wconversion # 类型转换
CFLAGS += -Wsign-conversion
# 将警告视为错误(发布版本)
CFLAGS_RELEASE += -Werror
7. 代码审查检查清单¶
优化代码审查时检查: - [ ] 优化级别是否合适? - [ ] 是否有未定义行为? - [ ] volatile是否正确使用? - [ ] 是否违反严格别名规则? - [ ] 时序敏感代码是否受影响? - [ ] 是否有浮点数精度问题? - [ ] 是否有竞态条件? - [ ] 优化是否影响可调试性? - [ ] 是否进行了充分测试? - [ ] 优化设置是否文档化?
常见陷阱¶
1. 过度优化¶
问题:盲目追求最高优化级别。
后果: - 代码大小增加(-O3循环展开) - 编译时间过长 - 引入难以调试的bug - 可能违反实时性要求
解决方案: - 使用-O2作为默认选择 - 只对热点代码使用-O3 - 测量实际性能提升 - 权衡代码大小和速度
2. 忽略volatile¶
问题:硬件寄存器或共享变量没有使用volatile。
后果: - 编译器优化掉必要的读写 - 中断标志不更新 - 硬件状态读取错误
解决方案:
// 正确:硬件寄存器使用volatile
#define UART_STATUS (*(volatile uint32_t*)0x40013800)
// 正确:中断标志使用volatile
volatile bool data_ready = false;
// 正确:多线程共享变量
volatile int shared_counter = 0;
3. 假设优化行为¶
问题:依赖特定的优化行为。
后果: - 代码在不同优化级别表现不同 - 移植到其他编译器失败 - 难以维护
解决方案: - 编写符合标准的代码 - 不依赖未定义行为 - 测试多个优化级别 - 使用编译器无关的写法
4. 忽略对齐要求¶
问题:未对齐的数据访问。
后果: - 性能下降 - 某些架构上崩溃(ARM Cortex-M0) - DMA传输失败
解决方案:
// 使用对齐属性
__attribute__((aligned(4)))
uint8_t buffer[1024];
// 检查对齐
if ((uintptr_t)ptr % 4 != 0) {
// 未对齐
}
5. 浮点数比较¶
问题:直接比较浮点数相等。
后果: - 优化可能改变比较结果 - 精度问题导致错误
解决方案:
6. 循环依赖¶
问题:循环中的数据依赖阻止优化。
后果: - 无法向量化 - 无法并行化 - 性能不佳
解决方案:
// 问题:循环依赖
for (int i = 1; i < size; i++) {
arr[i] = arr[i-1] + 1; // 依赖前一个元素
}
// 解决:重构算法
for (int i = 0; i < size; i++) {
arr[i] = base + i; // 无依赖
}
7. 内联失败¶
问题:期望内联但编译器没有内联。
后果: - 性能不如预期 - 函数调用开销
解决方案:
// 使用always_inline
static inline __attribute__((always_inline))
int must_inline(int x) {
return x * 2;
}
// 检查是否内联
// gcc -O2 -Winline source.c
实践练习¶
练习1:优化级别对比¶
编写一个程序,比较不同优化级别的性能和代码大小。
要求: - 实现矩阵乘法函数 - 编译为-O0, -O1, -O2, -O3, -Os - 测量执行时间和代码大小 - 分析结果
练习2:编写编译器友好的代码¶
优化以下代码:
int sum_positive(int* arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
if (arr[i] > 0) {
sum = sum + arr[i];
}
}
return sum;
}
提示: - 使用const和restrict - 考虑循环展开 - 避免分支
练习3:分析汇编代码¶
编译以下代码并分析生成的汇编:
要求: - 比较-O0和-O2的汇编 - 识别应用的优化技术 - 计算指令数量
练习4:修复优化相关的bug¶
以下代码在-O2优化时有bug,找出并修复:
void wait_for_flag(void) {
bool flag = false;
// 启动异步操作,会设置flag
start_async_operation(&flag);
// 等待完成
while (!flag) {
// 等待
}
}
提示:考虑volatile
自测问题¶
问题1:优化级别的选择¶
问题:在医疗器械软件开发中,应该如何选择编译器优化级别?为什么不推荐使用-O3或-Ofast?
点击查看答案
**答案**: **推荐的优化级别选择**: 1. **开发阶段**:-Og 或 -O0 - 最佳调试体验 - 变量可见,代码结构清晰 - 快速编译,快速迭代 2. **测试阶段**:-O2(与发布版本相同) - 及早发现优化相关的bug - 验证实际性能 - 保留部分调试信息(-g) 3. **发布阶段**: - **性能优先**:-O2 - **代码大小优先**:-Os - 医疗器械推荐:-O2 或 -Os **不推荐-O3的原因**: 1. **不确定性增加**: - 更激进的优化可能引入不可预测的行为 - 循环展开可能导致代码大小显著增加 - 某些优化可能影响实时性 2. **性能提升有限**: - 相比-O2,性能提升通常<10% - 某些情况下反而变慢(指令缓存失效) - 不值得增加的风险 3. **调试困难**: - 代码结构变化更大 - 变量更容易被优化掉 - 难以定位问题 **不推荐-Ofast的原因**: 1. **违反标准**: - 启用-ffast-math,不严格遵守IEEE 754 - 可能改变浮点数计算结果 - 医疗器械不可接受 2. **未定义行为**: - 假设不会溢出 - 假设没有NaN或Inf - 可能导致错误的计算结果 3. **监管风险**: - 难以验证和确认 - 不符合IEC 62304要求 - 可能无法通过认证 **最佳实践**:问题2:volatile关键字的作用¶
问题:什么时候必须使用volatile关键字?如果忘记使用volatile会发生什么?请举例说明。
点击查看答案
**答案**: **必须使用volatile的情况**: 1. **硬件寄存器访问**:// 正确
#define UART_DATA (*(volatile uint32_t*)0x40013804)
uint8_t read_uart(void) {
return (uint8_t)UART_DATA; // 每次都从硬件读取
}
// 错误:没有volatile
#define UART_DATA_WRONG (*(uint32_t*)0x40013804)
uint8_t read_uart_wrong(void) {
// 编译器可能优化为只读取一次
uint8_t data1 = (uint8_t)UART_DATA_WRONG;
uint8_t data2 = (uint8_t)UART_DATA_WRONG; // 可能被优化掉
return data2;
}
// 正确
volatile bool data_ready = false;
void main_loop(void) {
while (!data_ready) {
// 等待中断设置标志
}
process_data();
}
void UART_IRQHandler(void) {
data_ready = true; // 中断中设置
}
// 错误:没有volatile
bool data_ready_wrong = false;
void main_loop_wrong(void) {
// 编译器可能优化为:
// if (!data_ready_wrong) { while(1); }
// 因为在这个函数中data_ready_wrong永远不变
while (!data_ready_wrong) {
// 死循环!
}
}
// 正确
volatile int shared_counter = 0;
void thread1(void) {
shared_counter++; // 每次都从内存读写
}
void thread2(void) {
int value = shared_counter; // 每次都从内存读取
}
void function(void) {
volatile int important_value = 0; // 必须volatile
if (setjmp(buf) == 0) {
important_value = 42;
longjmp(buf, 1);
} else {
// important_value仍然是42
}
}
// 错误
void delay(uint32_t count) {
for (uint32_t i = 0; i < count; i++) {
// 空循环
}
}
// -O2优化后,整个函数可能变成:
void delay(uint32_t count) {
// 空函数!
}
// 正确
void delay(uint32_t count) {
volatile uint32_t i;
for (i = 0; i < count; i++) {
// volatile防止优化
}
}
问题3:函数内联的权衡¶
问题:函数内联有哪些优点和缺点?什么时候应该使用__attribute__((always_inline)),什么时候应该使用__attribute__((noinline))?
点击查看答案
**答案**: **函数内联的优点**: 1. **消除函数调用开销**: - 无需保存/恢复寄存器 - 无需跳转指令 - 无需栈操作 2. **允许进一步优化**: - 常量传播 - 死代码消除 - 公共子表达式消除 3. **提高性能**: - 减少指令数 - 更好的指令流水线 - 减少分支预测失败 **函数内联的缺点**: 1. **增加代码大小**: - 每个调用点都复制函数体 - 可能导致指令缓存失效 - Flash空间占用增加 2. **编译时间增加**: - 更多的代码需要优化 - 编译速度变慢 3. **调试困难**: - 无法设置断点 - 调用栈不清晰 - 难以定位问题 **使用always_inline的场景**:// 1. 非常小的函数(1-3行)
static inline __attribute__((always_inline))
int square(int x) {
return x * x;
}
// 2. 性能关键的热点函数
static inline __attribute__((always_inline))
uint32_t read_timestamp(void) {
return DWT_CYCCNT; // 必须快速
}
// 3. 包含特殊指令的函数
static inline __attribute__((always_inline))
void enable_interrupts(void) {
__asm__ volatile("cpsie i");
}
// 4. 类型安全的宏替代
static inline __attribute__((always_inline))
int max(int a, int b) {
return (a > b) ? a : b;
}
// 1. 调试和性能分析
__attribute__((noinline))
void debug_checkpoint(const char* msg) {
// 需要在调用栈中可见
log_message(msg);
}
// 2. 大函数(避免代码膨胀)
__attribute__((noinline))
void complex_algorithm(void) {
// 100+行代码
// 内联会显著增加代码大小
}
// 3. 很少调用的函数
__attribute__((noinline))
void error_handler(int error_code) {
// 错误处理,很少执行
// 不值得内联
}
// 4. 需要函数指针的函数
__attribute__((noinline))
void callback_function(void) {
// 需要获取函数地址
}
// 5. 递归函数
__attribute__((noinline))
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 好的内联候选
static inline int abs_fast(int x) {
int mask = x >> 31;
return (x + mask) ^ mask;
}
// 不好的内联候选
void process_large_data(uint8_t* data, size_t len) {
// 50+行代码
// 多个循环
// 复杂逻辑
// 应该noinline
}
// 平衡的方法
static inline int calculate_checksum(const uint8_t* data, size_t len) {
// 小的包装函数,可以内联
return calculate_checksum_impl(data, len);
}
__attribute__((noinline))
static int calculate_checksum_impl(const uint8_t* data, size_t len) {
// 实际实现,不内联
int sum = 0;
for (size_t i = 0; i < len; i++) {
sum += data[i];
}
return sum;
}
问题4:循环优化技术¶
问题:列举至少三种循环优化技术,并解释每种技术的原理和适用场景。
点击查看答案
**答案**: **1. 循环展开(Loop Unrolling)** **原理**: 将循环体复制多次,减少循环控制开销。 **示例**:// 原始循环
for (int i = 0; i < 100; i++) {
sum += array[i];
}
// 展开4次
for (int i = 0; i < 100; i += 4) {
sum += array[i];
sum += array[i+1];
sum += array[i+2];
sum += array[i+3];
}
// 处理剩余元素
// 优化前
for (int i = 0; i < size; i++) {
result[i] = array[i] * (factor + 10); // (factor+10)不变
}
// 优化后
int scale = factor + 10; // 外提
for (int i = 0; i < size; i++) {
result[i] = array[i] * scale;
}
// 优化前
for (int i = 0; i < 100; i++) {
array[i * 4] = 0; // 每次迭代都乘法
}
// 优化后
int offset = 0;
for (int i = 0; i < 100; i++) {
array[offset] = 0;
offset += 4; // 加法替代乘法
}
// 优化前
for (int i = 0; i < size; i++) {
a[i] = a[i] * 2;
}
for (int i = 0; i < size; i++) {
b[i] = b[i] + 10;
}
// 优化后
for (int i = 0; i < size; i++) {
a[i] = a[i] * 2;
b[i] = b[i] + 10;
}
// 优化前
for (int i = 0; i < size; i++) {
a[i] = expensive_calc1(data[i]);
b[i] = expensive_calc2(data[i]);
}
// 优化后(如果a和b的计算独立)
for (int i = 0; i < size; i++) {
a[i] = expensive_calc1(data[i]);
}
for (int i = 0; i < size; i++) {
b[i] = expensive_calc2(data[i]);
}
// 优化前(列优先访问,缓存不友好)
for (int j = 0; j < N; j++) {
for (int i = 0; i < M; i++) {
matrix[i][j] = 0; // 跳跃访问
}
}
// 优化后(行优先访问,缓存友好)
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = 0; // 连续访问
}
}
// 原始代码
void process_data(int* input, int* output, int size, int factor) {
for (int i = 0; i < size; i++) {
output[i] = input[i] * (factor + 10) * 2;
}
}
// 应用多种优化
void process_data_optimized(int* restrict input,
int* restrict output,
int size, int factor) {
// 1. 不变量外提
int scale = (factor + 10) * 2;
// 2. 循环展开(4次)
int i;
for (i = 0; i < size / 4 * 4; i += 4) {
output[i] = input[i] * scale;
output[i+1] = input[i+1] * scale;
output[i+2] = input[i+2] * scale;
output[i+3] = input[i+3] * scale;
}
// 3. 处理剩余元素
for (; i < size; i++) {
output[i] = input[i] * scale;
}
}
问题5:优化对调试的影响¶
问题:编译器优化会如何影响调试?如何在保持一定性能的同时改善调试体验?
点击查看答案
**答案**: **优化对调试的影响**: 1. **变量被优化掉**:int calculate(int a, int b) {
int temp = a + b; // -O2: temp可能不存在
int result = temp * 2;
return result;
}
// -O2优化后可能变成:
int calculate(int a, int b) {
return (a + b) * 2; // temp和result都不存在
}
// GDB中:
(gdb) print temp
// 输出:<optimized out>
void function(void) {
statement1();
statement2();
statement3();
}
// 优化后执行顺序可能是:
// statement2() -> statement1() -> statement3()
// 单步调试时跳来跳去
inline int helper(int x) {
return x * 2;
}
void main_function(void) {
int result = helper(10); // 被内联
}
// 调试时无法进入helper函数
// 调用栈中看不到helper
// 禁用特定函数的优化
__attribute__((optimize("O0")))
void debug_this_function(void) {
// 这个函数不会被优化
int temp = calculate(); // temp可见
process(temp);
}
// 其他函数使用正常优化
void normal_function(void) {
// 使用-O2优化
}
void function(void) {
volatile int debug_var = calculate();
// debug_var不会被优化掉
process(debug_var);
}
// 禁用特定函数的内联
__attribute__((noinline))
int helper(int x) {
return x * 2; // 可以设置断点
}
// 或使用编译选项
// gcc -fno-inline
#ifdef DEBUG
#define DEBUG_VAR(x) volatile typeof(x) debug_##x = (x)
#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define DEBUG_VAR(x)
#define DEBUG_PRINT(fmt, ...)
#endif
void function(void) {
int result = calculate();
DEBUG_VAR(result); // 调试版本保留变量
DEBUG_PRINT("Result: %d\n", result);
}
# 核心算法:无优化(便于调试)
core_algorithm.o: core_algorithm.c
$(CC) -O0 -g3 -c $< -o $@
# 外围代码:优化(提高性能)
peripheral.o: peripheral.c
$(CC) -O2 -g -c $< -o $@
# 链接
program: core_algorithm.o peripheral.o
$(CC) -o $@ $^
#include <assert.h>
void function(int* data, size_t len) {
assert(data != NULL);
assert(len > 0);
// 处理数据
for (size_t i = 0; i < len; i++) {
assert(i < len); // 边界检查
data[i] = process(data[i]);
}
}
# 即使优化也保留调试信息
CFLAGS_RELEASE = -O2 -g -DNDEBUG
# 生成单独的调试符号文件
program.debug: program
objcopy --only-keep-debug program program.debug
strip program
objcopy --add-gnu-debuglink=program.debug program
# 查看优化后的变量
(gdb) info locals
# 某些变量显示<optimized out>
# 查看寄存器(变量可能在寄存器中)
(gdb) info registers
# 查看汇编
(gdb) disassemble
# 设置条件断点
(gdb) break function if variable == 42
# 使用watchpoint
(gdb) watch variable
// 项目结构
// debug_build/ - 使用-Og,完整调试信息
// release_build/ - 使用-O2,优化性能
// test_build/ - 使用-O2 -g,测试优化版本
// 条件编译
#ifdef DEBUG
#define OPTIMIZE_LEVEL "O0"
#define INLINE_ATTR
#else
#define OPTIMIZE_LEVEL "O2"
#define INLINE_ATTR __attribute__((always_inline))
#endif
// 使用
INLINE_ATTR
static inline int fast_function(int x) {
return x * 2;
}
参考文献¶
-
GCC Documentation - GCC Optimization Options https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
-
ARM Compiler User Guide - Optimization for Performance and Code Size https://developer.arm.com/documentation/
-
Agner Fog (2021) - Optimizing software in C++ https://www.agner.org/optimize/
-
IEC 62304:2006 - Medical device software - Software life cycle processes
-
MISRA C:2012 - Guidelines for the use of the C language in critical systems
-
Drepper, U. (2007) - What Every Programmer Should Know About Memory
相关资源¶
最后更新:2026-02-09
版本:1.0
作者:医疗器械嵌入式软件知识体系项目组
💬 讨论区
欢迎在这里分享您的想法、提出问题或参与讨论。需要 GitHub 账号登录。