简图记录学习~
一、概念
1、预处理概念:C程序在编译过程,首先进行预处理,包括 删除注释、递归包含#include文件、处理预处理指令、暂开宏定义。
ANSI标准定义C语言预处理指令:
(1)宏定义: #define 撤销宏定义#undef
(2)条件编译指令: #if 常量表达式、#ifdef 标识符、 #ifndef 标识符、#else、#elif、#endif
(3)#error指令:编译时遇到#error error-message会产生一个编译错误提示并且停止编译,印出error-message
(4)#line指令:编译时遇到 #line 行号[文件名] 会改变当前编译的行号和文件名
(5)#pragma指令:编译遇到 #pragma 指令名 会设定编译器状态或者执行指定动作,与编译器/操作系统相关;
2、常见编译宏
__LINE__行号、__FUNCTION__函数名、__FILE__文件名、__DATE__编译日期(如"25 Dec 2007")、__TIME__编译时间(如"12:30:35")
3、文件包含方法:两种格式
(1)#include <filename>到系统路径取获取文件
(2)#include "filename"先从当前目录查找,如果没有就去系统目录下;
4、防止文件重复包含宏定义:
为了防止头文件被重复包含,常在.h文件用以下格式:
#ifndef XXXXX //常用文件名简写
#define XXXXX
/*头文件代码*/
#endif
二、宏详解
该文件内部从定义开始直到文件尾或者撤销定义前,都在预处理阶段展开为其定义内容。可在文件任何行定义。
1、宏定义约束
(1)宏不能以分号;结尾
(2)字符串内容不会认为是宏定义进行处理
(3)注意宏处理是在 删除注释后,不要使用宏定义//等注释符号,不会得到处理;
2、宏定义常量用法:
(1)用于定义多处使用常量,且可能需要统一修改,如#define PI 3.1415926;
(2)用语提升可读性,如错误码定义,文件路径定义。
3、宏定义表达式
如#define SEC_A_YEAR (60*60*24*356)UL--->考虑在16位机上可能溢出使用长整型L,且时间为无符号U
4、宏定义宏函数(带参数表达式)
#define AREA(a,b) ((a)*(b)) //注意括号保护,思考AREA(1+2,3+5); AREA后不能有空格,否则为定义AREA为(a,b) ((a)*(b))
#define S(r) ((r)*(r)) //注意宏直接替换的特性和函数之间的差别,如S(r++)
当进行多条语句宏命令时,运用do while(0)保护防止出错,如
#define CHECK_PTR(p) \
do{\
if (NULL == p){\
printf("ptr is NULL\n");\
reurn -1;
}\
}while(0);
//设想如果没有加do wiile(0)如果使用if (...) CHECK_PTR(p);else return 0;
5、几种特殊运算符宏函数
(1)# :将带参宏定义入参转化为双引号括起来的字符串
#define TO_STR(x) #x---->TO_STR(abc)展开为"abc"
#define TO_STR(x) #x
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))
int main(void) {
int y = 5;
PSQR(y); // 输出 The square of y is 25.
PSQR(2 + 4); // 输出 The square of 2 + 4 is 36.
printf(TO_STR(y) " is %d\n", y); // 输出 y is 5
printf(TO_STR(2 + 4) " is %d\n", 2 + 4); // 输出 2 + 4 is 6
return 0;
}
(2)#@ :将带参宏定义单字符入参转化为单引号括起来的字符(不是标准C支持的语法,gcc编译报错,建议不使用)
#define makechar(x) @#x---->makechar(a)展开为'a'
(3)## :将带参宏定义参数名连接成实际参数名
#define XNAME(x) Num##x---->XNAME(9)展开为Num9
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void) {
int XNAME(1) = 14; // 变成 int x1 = 14;
int XNAME(2) = 20; // 变成 int x2 = 20;
int x3 = 30;
PRINT_XN(1); // 变成 printf("x1 = %d\n", x1); 输出x1 = 14
PRINT_XN(2); // 变成 printf("x2 = %d\n", x2); 输出x2 = 20
PRINT_XN(3); // 变成 printf("x3 = %d\n", x3); 输出x3 = 30
return 0;
}
(4)变参宏... 与 __VA_ARGS__
:一些函数(如 printf())接受数量可变的参数,C99/C11也对宏提 供了这样的工具,通过把宏参数列表中最后的参数写成省略号(即,3个点...)来实现,省略号只能代替最后的宏参数(只能放到最后)。__VA_ARGS__可用在替换部分中,表明省略号代表什么。
#define SIMPLE_PR1(a) printf(a) // 只能输入一个参数,如果如SIMPLE_PR1("output %d\n", 123)会报错,有两个参数
#define SIMPLE_PR2(...) printf(__VA_ARGS__)
#define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)
int main(void) {
double x = 48;
double y;
y = sqrt(x);
PR(1, "x = %g\n", x); // 输出 Message 1: x = 48
PR(2, "x = %.2f, y = %.4f\n", x, y); // 输出 Message 2: x = 48.00, y = 6.9282
SIMPLE_PR2("output %d\n", 456); // 输出 output 456
return 0;
}
6、编译上宏使用技巧
常用宏对系统函数如memset、printk、malloc等进行封装,提升代码可移植性。
在特殊情况如boot下需要复用驱动代码可对驱动下特有的函数进行定义空解决编译问题。
三、pragma指令详解
#pragam message("消息文本")--->编译时遇到进行打印
#pragma code_seg(["段名,["段class"]])--->编译时指定代码d段
#pragma once--->保证头文件只编译一次(兼容性不太好)
#pragma pack(n)--->指定结构体内存对齐时,每个成员偏移为最大n倍数(当n>成员size时)
如:
#pragma pack(4)
struct test{
char a;//偏移0,占位1,b自然对齐补占位1
short b;//偏移2,占位2
int c;//偏移4,占位4
char *d;//偏移8,占位4
double e;//编译12(如果没有pack命令,自然对齐偏移应该为16保证8倍数),占位8
}
#pragma pack()//恢复默认对齐方式
/*sizeof(struct test)=20*/