C语言解析

编译器

GCC编译器扩展机制 语法 使用

attribute 是 GCC 编译器的扩展机制,用于为函数、变量、类型添加特殊属性,控制编译器的行为。

__attribute__ 是 GCC 编译器的扩展机制,用于为函数、变量、类型添加特殊属性,控制编译器的行为。

基本语法

__attribute__((属性1, 属性2, ...))

/* 双层括号是必须的! */
__attribute__((section(".mydata")))     /* 正确 */
__attribute__(section(".mydata"))       /* 错误!缺少括号 */

1. 变量声明

/* 位置1:类型之后,变量名之前(推荐) */
int __attribute__((section(".mydata"))) var = 5;

/* 位置2:整个声明之前 */
__attribute__((section(".mydata"))) int var = 5;

/* 位置3:变量名之后 */
int var __attribute__((section(".mydata"))) = 5;

/* 多个属性 */
uint32_t __attribute__((section(".dma_buf"), aligned(32))) dma_buffer[256];

2. 函数声明

/* 位置1:返回类型之前 */
__attribute__((section(".ramfunc"))) void func(void);

/* 位置2:返回类型之后 */
void __attribute__((section(".ramfunc"))) func(void);

/* 位置3:函数名之后,参数列表之前 */
void func(void) __attribute__((section(".ramfunc")));

/* 位置4:函数定义 */
__attribute__((weak, alias("Default_Handler")))
void USART1_IRQHandler(void);

3. 结构体/类型定义

/* 结构体属性 */
struct __attribute__((packed)) MyStruct {
    uint8_t  a;
    uint32_t b;
};

/* typedef 中使用 */
typedef struct {
    uint8_t data[32];
} __attribute__((aligned(4))) AlignedBuffer;

常用属性详解

1. section - 指定存储段

/* 变量放入指定段 */
uint32_t __attribute__((section(".ccmram"))) fast_var;
uint8_t __attribute__((section(".dma_buf"))) dma_buffer[1024];

/* 函数放入指定段 */
__attribute__((section(".itcm")))
void critical_function(void) {
    // 代码放入ITCM RAM执行
}

/* 只读数据放入指定段 */
const uint32_t __attribute__((section(".rodata.custom"))) 
lookup_table[] = {0x1234, 0x5678};

2. aligned - 对齐方式

/* 32字节对齐(DMA常用) */
uint8_t __attribute__((aligned(32))) dma_buffer[1024];

/* 结构体对齐 */
struct __attribute__((aligned(16))) {
    uint32_t field1;
    uint32_t field2;
} aligned_struct;

/* Cache line对齐(通常32字节) */
__attribute__((aligned(32)))
uint8_t cache_aligned_buffer[256];

packed - 紧凑存储

/* 取消结构体对齐(节省空间) */
struct __attribute__((packed)) {
    uint8_t  a;  // 1字节
    uint32_t b;  // 4字节
    uint8_t  c;  // 1字节
} compact;       // 总共6字节(不是12字节)

/* 用于协议数据结构 */
typedef struct __attribute__((packed)) {
    uint8_t  header;
    uint16_t length;
    uint32_t data;
    uint8_t  checksum;
} ProtocolPacket;  // 确保与协议规定完全一致

weak - 弱符号

/* 弱函数(可被覆盖) */
__attribute__((weak))
void Default_Handler(void) {
    while(1);
}

/* HAL库回调函数 */
__attribute__((weak))
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    /* 用户可以重新定义此函数 */
    UNUSED(huart);
}

used/unused - 使用标记

/* 防止被优化掉 */
__attribute__((used))
static const char version[] = "V1.0.0";

/* 消除未使用警告 */
__attribute__((unused))
static void debug_function(void) {
    // 仅在调试时使用
}

/* 参数未使用 */
void callback(__attribute__((unused)) void *param) {
    // param参数未使用,不会产生警告
}

** noinit - 不初始化**

/* 不在启动时初始化(节省启动时间) */
__attribute__((section(".noinit")))
uint8_t large_buffer[65536];

/* RAM保持数据 */
__attribute__((section(".backup_sram"), noinit))
uint32_t retain_data[256];

interrupt - 中断处理

/* ARM Cortex-M中断处理 */
__attribute__((interrupt("IRQ")))
void USART1_IRQHandler(void) {
    // 编译器自动生成中断进入/退出代码
}

optimize - 优化控制

/* 特定函数优化级别 */
__attribute__((optimize("O3")))
void performance_critical(void) {
    // 最大优化
}

__attribute__((optimize("O0")))
void debug_function(void) {
    // 不优化,便于调试
}

always_inline/noinline

/* 强制内联 */
__attribute__((always_inline))
static inline void fast_function(void) {
    // 总是内联展开
}

/* 禁止内联 */
__attribute__((noinline))
void no_inline_function(void) {
    // 永不内联
}

宏定义

后缀含义示例
uU无符号(unsigned)100u
lL长整型(long)100L
ulUL无符号长整型(unsigned long)100UL
llLL长长整型(long long)100LL
ullULL无符号长长整型(unsigned long long)100ULL
写法类型位数(通常)
16int(有符号)16/32 位
16uunsigned int16/32 位
16ULunsigned long32 位
16ULLunsigned long long64 位

关键字

  1. extern

在C语言中使用这种方式(头文件中extern声明,源文件中定义)是一种标准做法,主要有以下原因:

  1. 避免重复定义错误

    • 如果在头文件中直接定义变量 TIM_HandleTypeDef htim2;
    • 当多个C文件包含该头文件时,会导致多次定义同一个变量
    • 这会引发链接时的"multiple definition"错误
  2. extern的作用

    • extern 关键字告诉编译器"这个变量在其他地方已定义,这里只是声明"
    • 声明只是告知编译器变量存在,但不分配内存空间
    • 定义则会真正分配内存空间
  3. 一处定义规则

    • 在C语言中,全局变量应该只在一个源文件中定义
    • 其他需要使用该变量的文件通过extern声明引用它
    • 遵循"一次定义,多处声明"的原则

编程注意项

  1. 在C语言中,全局作用域(函数外部)只能进行声明和静态初始化,而不能执行运行时的赋值操作。这些uart = huart2;和tim = htim2;是运行时操作,必须放在函数内部执行。
   // 全局声明
   UART_HandleTypeDef *uart;
   // 错误:在全局域直接赋值操作
   *uart = huart2;  // 这行导致编译错误`
  1. C语言头文件包含顺序问题笔记

在C语言中,头文件的包含顺序非常重要,这是因为编译器按顺序处理每个头文件。如果一个头文件使用了另一个头文件中定义的类型或宏,但没有包含那个头文件,就会产生"未知类型"的编译错误。

问题示例

  • mb_m.h文件中使用了eMBErrorCodeUCHAR等类型
  • 但这些类型实际上是在mb.h中定义的
  • mb_m.h自身没有包含mb.h
  • 解决方法:在包含mb_m.h之前先包含mb.h

最佳实践

  1. 头文件应自包含(包含它所依赖的所有头文件)
  2. 当遇到头文件依赖问题时,确保按正确顺序包含头文件
  3. 对于库开发者:使用头文件保护和前向声明避免这类问题

结构体

基础

C99 引入的指定初始化器(designated initializer) 语法,用来按成员名而不是按顺序给结构体/联合体成员赋值。左边 .member = value 中的点号(.)就是“成员选择符”;未出现的成员会被自动置 0(或 NULL0.0 等,视类型而定)。

/* 结构体声明 */
typedef struct {
    int a;
    char b;
    double c;
} Foo;

/* 指定初始化器 */
Foo f = {
    .b = 'X',
    .c = 3.14,
    /* .a 未写出,自动置 0 */
};

运算符

位运算

示例

#define BIT_SET(reg, bit)    ((reg) |= (1U << (bit)))
#define BIT_CLEAR(reg, bit)  ((reg) &= ~(1U << (bit)))
#define BIT_TOGGLE(reg, bit) ((reg) ^= (1U << (bit)))
#define BIT_READ(reg, bit)   (((reg) >> (bit)) & 1U)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值