宏定义与宏使用分析

来源:互联网 发布:元朝摔头胎 知乎 编辑:程序博客网 时间:2024/05/16 15:04

编译预处理

专题三:编译预处理。包括以下章节:

  • 编译过程简介
  • 宏定义与宏使用分析
  • 条件编译使用分析
  • #error和#line
  • #pragma预处理分析
  • #和##运算符使用解析

宏定义常量

  • #define定义的宏常量可以出现在代码的任何地方
  • 从定义宏开始,之后的代码都可以使用这个宏常量

以下定义的宏定义是否正确?
2-1.c

#define ERROR -1#define PI 3.1415926#define PATH_1 "D:\Dep\C\ts.txt"#define PATH_2 D:\Dep\C\ts.txt  //预编译器直接替换//以下定义:后边的\是接续符,相当于D:\DepCts.txt#define PATH_2 D:\Dep\C\    ts.txt              int main(){    return 0;}

执行命令:gcc -E 2-1.c -o 2-1.i
2-1.i

# 1 "2-1.c"# 1 "<built-in>"# 1 "<命令行>"# 1 "2-1.c"# 9 "2-1.c"//直接删除了宏定义,说明编译器认为定义的宏都是正确的int main(){    return 0;}

宏定义表达式

  • #define表达式给人一种函数调用的假象,其实她不是
  • #define表达式可以比函数更强大
  • #define表达式比函数更容易出错

2-2.c

#define SUM(a,b) (a)+(b)#define MIN(a,b) ((a)<(b) ? (a) : (b))#define DIM(a) (sizeof(a)/sizeof(*a))int sum(int a, int b){    return a+b;//或者(a)+(b)或者((a)+(b)),结果不变}int min(int a, int b){    return ((a)<(b) ? (a) : (b));//传入参数时已经计算完毕,即i++=1,b=2,不带入函数体内计算}int dim(int a[]){    return (sizeof(a)/sizeof(*a));}int main(){    //我们要的结果是6,而结果却是5    //宏定义应该定义为((a)+(b))    printf("%d\n", SUM(1,2)*SUM(1,2));    int i = 1;    int j = 5;    //我们预想的结果是1(1<5,返回i++),结果却是2    //预编译器直接替换printf("%d\n", ((i++)<(j) ? (i++) : (j)));    //i++执行了两次,所以i++=2    //使用宏定义时应该避免在宏参数中写表达式    printf("%d\n", MIN(i++, j));    int a[] = {1, 2, 3, 4, 5};    //打印结果为5,正确    printf("%d\n", DIM(a));    //调用函数,打印结果为1,错误    //函数参数退化为指针(int* a)。a为unsigned int类型。所以sizeof(a)/sizeof(*a)=1    //所以有时候,函数无法代替宏表达式    printf("%d\n", dim(a));    return 0;}

实例分析2-1:宏对于函数的优势

2-3.c

#include <stdio.h>#include <malloc.h>#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)#define FOREVER() while(1)#define BEGIN {#define END   }#define FOREACH(i, m) for(i=0; i<m; i++)int main(){    int array[] = {1, 2, 3, 4, 5};    int x = 0;    int*p = MALLOC(int, 5);    FOREACH(x, 5)    BEGIN    p[x] = array[x];    END    FOREACH(x, 5)    BEGIN    printf("%d\n", p[x]);    END    FOREVER();    free(p);    printf("Last printf...\n");    return 0;}

执行命令:gcc -E 2-3.c -o 2-3.i

2-3.i

# 1 "2-3.c"# 1 "<built-in>"# 1 "<命令行>"# 1 "2-3.c"# 1 "/usr/include/stdio.h" 1 3 4# 28 "/usr/include/stdio.h" 3 4# 1 "/usr/include/features.h" 1 3 4# 324 "/usr/include/features.h" 3 4# 1 "/usr/include/x86_64-linux-gnu/bits/predefs.h" 1 3 4# 325 "/usr/include/features.h" 2 3 4# 357 "/usr/include/features.h" 3 4# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4# 378 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4# 379 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4# 358 "/usr/include/features.h" 2 3 4# 389 "/usr/include/features.h" 3 4# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4/*......此处省略n行(展开的<stdio.h>和<malloc.h>)......*/# 3 "2-3.c" 2# 11 "2-3.c"int main(){    int array[] = {1, 2, 3, 4, 5};    int x = 0;    //预编译后宏定义直接替换    //宏定义强于函数1:宏定义可以传递类型参数(int),而函数只能传递形参(int a)    int*p = (int*)malloc(sizeof(int)*5);    //宏定义强于函数2:宏定义可以扩展c语言的关键字或者说用法    for(x=0; x<5; x++)    {    p[x] = array[x];    }    for(x=0; x<5; x++)    {    printf("%d\n", p[x]);    }    while(1);    free(p);    printf("Last printf...\n");    return 0;}

宏表达式与函数的对比

  • 宏表达式在预编译期间被处理,编译器不知道宏表达式
  • 宏表示式用“实参”完全替代形参,不进行任何计算
  • 宏表达式没有任何“调用”开销(在预编译期直接替换)
  • 宏表达式中不能出现递归定义
    • #define FAC(n) ((n>0) ? (FAC(n-1)+1) : 0)

2-4.c

#include<stdio.h>#define FAC(n) ((n>0) ? (FAC(n-1)+1) : 0)int main(){    int j = FAC(100);    //编译器报错却没有标明出错的行号    //而汇编器报错,是不会标明出错行号的    //如果发现编译报错却没有发现出错行号,可以一步一步编译(预编译->编译->汇编),来查错    printf("%d\n", j);    return 0;}

结果:
这里写图片描述

2-4.i

"2-4.c"# 1 "<built-in>"# 1 "<命令行>"# 1 "2-4.c"/*......此处省略n行(展开的<stdio.h>)......*/# 940 "/usr/include/stdio.h" 3 4# 2 "2-4.c" 2int main(){//预编译后直接替换宏表达式,会出现一个没有定义的FAC int j = ((100>0) ? (FAC(100 -1)+1) : 0); printf("%d\n", j); return 0;}

2-4.s

    .file   "2-4.c"    .section    .rodata.LC0:    .string "%d\n"    .text    .globl  main    .type   main, @functionmain:.LFB0:    .cfi_startproc    pushq   %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    subq    $16, %rsp    movl    $99, %edi    movl    $0, %eax    call    FAC  //调用FAC函数,发现没有定义FAC函数    addl    $1, %eax    movl    %eax, -4(%rbp)    movl    $.LC0, %eax    movl    -4(%rbp), %edx    movl    %edx, %esi    movq    %rax, %rdi    movl    $0, %eax    call    printf    movl    $0, %eax    leave    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE0:    .size   main, .-main    .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"    .section    .note.GNU-stack,"",@progbits

使用#undef

  • 宏定义的常量或表达式是否有作用域?
#include<stdio.h>int f1(int a, int b){    #define MIN(a,b) ((a)<(b) ? a :b)    return MIN(a,b);}int f2(int a, int b, int c){    return MIN(MIN(a,b),c);}int main(){    printf("%d\n", f1(1,2));    //编译没有出错,且结果正确    //所以宏定义可以出现在代码的任何地方,而且其以下都可以使用她    printf("%d\n", f2(1,2,3));    return 0;}

可以使用#undef来限制宏的作用域

#include<stdio.h>int f1(int a, int b){    #define MIN(a,b) ((a)<(b) ? a :b)    return MIN(a,b);    //加上#undef    //在return加上#undef是可行的,因为在预编译期进行的    #undef }int f2(int a, int b, int c){    return MIN(MIN(a,b),c);}int main(){    printf("%d\n", f1(1,2));    //编译出错    //所以宏定义可以扩展c语言,定义局部函数。(标准c语言中没有局部函数的概念)    printf("%d\n", f2(1,2,3));    return 0;}

强大的内置宏

这里写图片描述

手把手教你写代码:定义日志宏

2-6.c

#include<stdio.h>#include<time.h>#define LOG(s) do{                                                  \    time_t t;                                                       \    struct tm* ti;                                                  \    time(&t);                                                       \    ti = localtime(&t);                                             \    printf("%s[%s:%d] %s\n", asctime(ti), __FILE__, __LINE__, s);   \}while(0)                                                           //#define f1  (x)  ((x)-1) 错误的写法//以下这种写法没有错(0_0)#define f2( a,                 b  ) ((a)-(b))void f(){    LOG("ENTER f()...");    LOG("EXIT f()...");}int main(){    //日志宏不能用函数代替,因为行号不会变化。(每次调用都显示日志函数的行号)    LOG("Enter mian()...");    f();    LOG("Exit main()...");//  printf("%d\n", f1(6));    //调用正确(=^_^=),打印正确    printf("%d", f2(       1, 2                ));    return 0;}

2-6.i

# 1 "2-6.c"# 1 "<built-in>"# 1 "<命令行>"# 1 "2-6.c"/*......此处省略n行(展开的<stdio.h>和<time.h>)......*/# 417 "/usr/include/time.h" 3 4# 3 "2-6.c" 2# 16 "2-6.c"void f(){ do{ time_t t; struct tm* ti; time(&t); ti = localtime(&t); printf("%s[%s:%d] %s\n", asctime(ti), "2-6.c", 18, "ENTER f()..."); }while(0); do{ time_t t; struct tm* ti; time(&t); ti = localtime(&t); printf("%s[%s:%d] %s\n", asctime(ti), "2-6.c", 19, "EXIT f()..."); }while(0);}int main(){ do{ time_t t; struct tm* ti; time(&t); ti = localtime(&t); printf("%s[%s:%d] %s\n", asctime(ti), "2-6.c", 24, "Enter mian()..."); }while(0); f(); printf("%d", ((1)-(2))); do{ time_t t; struct tm* ti; time(&t); ti = localtime(&t); printf("%s[%s:%d] %s\n", asctime(ti), "2-6.c", 31, "Exit main()..."); }while(0); return 0;}

结果:
这里写图片描述

原创粉丝点击