C语言-预编译之宏定义(太太太详细了!!!)


一、预处理

  在C语言程序源码中,凡是以井号(#)开头的语句被称为预处理语句,这些语句严格意义上并不属于C语言语法的范畴,它们在编译的阶段统一由所谓预处理器(cc1来处理。所谓预处理,顾名思义,指的是真正的C程序编译之前预先进行的一些处理步骤,这些预处理指令包括:

  1. 头文件: #include
  2. 定义宏: #define
  3. 取消宏: #undef
  4. 条件编译: #if、#ifdef、#ifndef、#else、#elif、#endif
  5. 显示错误: #error
  6. 修改当前文件名和行号: #line
  7. 向编译器传送特定指令: #progma

基本语法

一个逻辑行只能出现一条预处理指令,多个物理行需要用反斜杠连接成一个逻辑行。

  预处理是整个编译全过程的第一步:预处理编译汇编链接。

  可以通过如下编译选项来指定来限定编译器只进行预处理操作:

gcc example.c -o example.i -E

二、宏的概念

宏(macro)
实际上就是一段特定的字串,在源码中用以替换为指定的表达式。例如:

#define PI 3.14

此处,PI就是宏(宏一般习惯用大写字母表达,以区分于变量和函数,但这并不是语法规定,只是一种习惯)是一段特定的字串,这个字串在源码中出现时,将被替换为3.14。例如:

int main()
{
  printf("圆周率: %f\n",PI);
  // 此语句将被替换为: printf("圆周率: %f\n”,3.14);
}

宏的作用

  使得程序更具可读性:字串单词一般比纯数字更容易让人理解其含义。

  使得程序修改更易行:修改宏定义,即修改了所有该宏替换的表达式。

  提高程序的运行效率:程序的执行不再需要函数切换开销,而是就地展开。

三、无参宏

  无参宏意味着使用宏的时候,无需指定任何参数,比如

#define PI    3.14
#define SCREEN SIZE  800*480*4
int main()
{
    //在代码中,可以随时使用以上无参宏,来替代其所代表的表达式;
    printf("圆周率: %f\n",PI);
    mmap(NULL,SCREEN SIZE,PROT_READ|PROT WRITE,MAP_SHARED,...);
}

注意到,上述代码中,除了有自定义的宏,还有系统预定义的宏:

// 自定义宏:
#define PI    3.14
#define SCREEN SIZE 800*480*4

// 系统预定义宏
#define NULL ((void *)o)
#define PROT READ     0x1  /* Page can be read. */
#define PROT WRITE    0x2  /* Page can be written. */
#define MAP SHARED    0X01 /* share changes. */

宏的最基本特征是进行直接文本替换,以上代码被替换之后的结果是:

int main()
{
    printf("圆周率: %f\n",3.14);
    mmap(((void *)o),800*480*4,@x1|@x2,@x01, ...);
}

 四、带参宏

带参宏意味着宏定义可以携带“参数”,从形式上看跟函数很像,例如:


#define MAx(a,b)    a>b ? a :b
#define MIN(a,b)    a<b ? a : b

以上的MAX(a,b)和 MIN(a,b)都是带参宏,不管是否带参,宏都遵循最初的规则,即宏是一段待替换的文本,例如在以下代码中,宏在预处理阶段都将被替换掉:

int main()
{
int x = 100,y = 200;
printf("最大值:%d\n”,MAx(x,y));
printf("最小值:%d\n",MIN(x,y));

// 以上代码等价于:
// printf("最大值:%d\n",x>y ? x : y);
// printf("最小值:%d\n",x<y ? x : y);
}

带参宏的特点:

  1. 直接文本替换,不做任何语法判断,更不做任何中间运算。
  2. 宏在编译的第一个阶段就被替换掉,运行中不存在宏
  3. .宏将在所有出现它的地方展开,这一方面浪费了内存空间,另一方面有节约了切换时间。

五、参宏的副作用

由于宏仅仅做文本替换,中间不涉及任何语法检查、类型匹配、数值运算,因此用起来相对函数要麻烦很多。例如:

#define MAx(a, b) a>b ? a : b

int main()
{
    int x = 100,y = 200;
    printf("最大值:%d\n",MAX(x,y==200?888:999));
}

直观上看,无论y的取值是多少,表达式y==200888:999 的值一定比要大,但由于宏定义仅仅是文本替换中间不涉及任何运算,因此等价于:

printf("最大值:%d\n",x>y==200?888:999 ? x : y==200?888:999);

可见,带参宏的参数不能像函数参数那样视为一个整体,整个宏定义也不能视为一个单一的数据,事实上,不管是宏参数还是宏本身,都应被视为一个字串,或者一个表达式,或者一段文本,因此最基本的原则是:
  将宏定义中所有能用括号括起来的部分,都括起来,比如:

#define MAx(a, b) ((a)>(b) ? (a) : (b))

六、宏定义中的符号粘贴


有些时候,宏参数中的符号并非用来传递数据,而是用来形成多种不同的字串,例如在某些系统函数中,系统本身规范了函数接口的部分标准,形如:

void  zinitcall service 1(void)
{
    ...
}

void   zinitcall_service_2(void)
{
    ...
}

void  zinitcall feature 1(void)
{
    ...
}

void  zinitcall feature 2(void)
{
    ...
}

此时,若需要向用户提供一个方便整合字串的宏定义,可以这么写:

#define LAYER INITCALL(num,layer)  __zinitcall_##layer##_##num

用户的调用如下:

LAYER INITCALL(service,1);
LAYER INITCALL(service,2);
LAYER INITCALL(feature,1);
LAYER INITCALL(feature,2);

注意:

在书写非字符串的字串时(如上述例子),使用两边双共号来粘贴字串,并且要注意如果字串出现在最末尾,则最后的双井号必须去除,例如上述代码不可写成:

#define LAYER INITCALL(num,layer)  __zinitcall_##layer##_##num#t#

但如果粘贴的字串并非出现在最未尾,则前后都必须加上双井号:

#define LAYER INITCALL(num,layer)  __zinitcall_##layer##_##num##end

注意:

另外,如果字串本身拼接为字符串,那么只需要使用一个井号即可,比如:

#define domainName(a,b)www.#a#D.com

int main()
{
    printf("%s\n",domainName(yueqian, lab));
}

执行打印如下:

gec@ubuntu:~$ ./a.out
www.yueqian .lab.com
gec@ubuntu:~$

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
汇编语言是一种低级编程语言,它直接操作计算机硬件。下面是求最大值、最小值和平均值的示例程序: ``` data segment arr db 30h, 25h, 40h, 10h, 35h ;数据数组 n dw 5 ;数据个数 max db 0 ;最大值 min db 0 ;最小值 sum dw 0 ;总和 avg dw 0 ;平均值 data ends code segment start: mov ax, data mov ds, ax ;求最大值 mov bl, arr mov bh, bl mov cx, n max_loop: cmp [bl], [bh] jg max_update inc bl jmp max_check_end max_update: mov bh, bl max_check_end: inc bl loop max_loop mov max, [bh] ;求最小值 mov bl, arr mov bh, bl mov cx, n min_loop: cmp [bl], [bh] jl min_update inc bl jmp min_check_end min_update: mov bh, bl min_check_end: inc bl loop min_loop mov min, [bh] ;求平均值 mov bx, arr mov cx, n sum_loop: add ax, [bx] inc bx loop sum_loop mov sum, ax mov ax, sum cwd ;扩展符号位,用于除法计算,将AX转换为DX:AX idiv n ;除以n,商在AX,余数在DX mov avg, ax ;显示结果 mov ah, 09h ;DOS功能号,显示字符串 lea dx, max_msg ;最大值字符串地址 int 21h ;调用DOS中断,显示字符串 mov al, max ;将最大值转换为ASCII码,方便显示 add al, 30h ;转换为ASCII码的方式是加上'0' mov ah, 0Eh ;DOS功能号,显示字符 int 10h ;调用BIOS中断,显示字符 lea dx, min_msg ;最小值字符串地址 int 21h ;调用DOS中断,显示字符串 mov al, min ;将最小值转换为ASCII码,方便显示 add al, 30h ;转换为ASCII码的方式是加上'0' mov ah, 0Eh ;DOS功能号,显示字符 int 10h ;调用BIOS中断,显示字符 lea dx, avg_msg ;平均值字符串地址 int 21h ;调用DOS中断,显示字符串 mov ax, avg ;将平均值转换为ASCII码,方便显示 call print_word ;调用子程序,显示16位数值 exit: mov ah, 4Ch ;DOS功能号,程序结束退出 int 21h ;调用DOS中断,程序结束退出 print_word proc near ;子程序,显示16位数值,入口参数:AX=数值(十进制) push ax ;保存寄存器内容 push dx mov bx, 10d ;除数为10d mov cx, 0 ;数字长度初始化为0 L1: xor dx, dx ;清零DX寄存器 div bx ;AX=AX/BX,商在AL,余数在AH push dx ;保存余数 inc cx ;数字长度+1 test ax, ax ;判断是否还能继续除法运算 jnz L1 L2: pop dx ;弹出余数 add dl, '0' ;转换为ASCII码 mov ah, 02h ;DOS功能号,显示字符 int 21h ;调用DOS中断,显示字符 loop L2 pop dx ;恢复寄存器内容 pop ax ret print_word endp max_msg db 'The maximum value is: ' min_msg db 'The minimum value is: ' avg_msg db 'The average value is: $' code ends end start ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值