当前位置 博文首页 > Linux猿:define 面试知识点都在这里了!

    Linux猿:define 面试知识点都在这里了!

    作者:[db:作者] 时间:2021-09-17 09:04

    ?作者:Linux猿

    ?简介:CSDN博客专家,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊!

    ?关注专栏:C/C++面试通关集锦?(优质好文持续更新中……)

    define 是预处理器的一个指令,定义在 C 语言中。在预处理过程中,将宏替换为对应宏值,可以理解为字符串的替换。

    为什么说它是预处理器的一个指令呢?

    我们来看一个简单的例子:

    #include <stdio.h>
    #define x 5
    int main() {
        int y = x + 3;
        printf("y = %d\n", y);
    }

    ?在上面的代码中,定义了一个宏 x ,其值为 5,我们只将代码预处理一下,执行如下命令:

    gcc -E -o main.i main.c

    预处理:完成头文件的插入、宏定义的展开以及条件编译的处理等。

    执行命令后,在当前目录下生成了文件 main.i,部分内容如下所示:

    ......
    ......
    # 4 "main.c"
    int main() {
        int y = 5 + 3;
        printf("y = %d\n", y);
    }
    ......
    ......

    可以看到,宏 x 在预处理后已被替换为 5了。

    当然,宏定义也可以是更复杂的形式,比如:表达式、函数等,来看一个例子:

    #include <stdio.h>
    
    #define MAX(x, y) ((x) > (y)) ? (x) : (y)
    int main() {
        printf("max = %d\n", MAX(2+3, 4+5));
    }

    输出结果为:

    linuxy@linuxy:~/defineDir$ ./main 
    max = 9
    linuxy@linuxy:~/defineDir$

    来看一下预处理后的结果(main.i文件部分内容):

    ......
    ......
    # 4 "main.c"
    int main() {
        printf("max = %d\n", ((2+3) > (4+5)) ? (2+3) : (4+5));
    }
    ......
    ......

    可以看到直接将定义的宏进行了替换。

    但是,这个替换可能会导致如下问题:

    问题 1.?define 并不做类型检查

    define 只做值替换,而不像普通变量那样做类型的检查。

    来看一个例子:

    #include <stdio.h>
    
    #define PLUSONE(x) ++x 
    int main() {
        int valInt = 10;
        double valDou = 20;
        char *p = "LinuxY";
        printf("valInt = %d\n", PLUSONE(valInt));
        printf("valDou = %lf\n", PLUSONE(valDou));
        printf("p = %s\n", PLUSONE(p));
    }

    输出结果为:

    linuxy@linuxy:~/defineDir$ gcc main.c -o main
    linuxy@linuxy:~/defineDir$ ./main 
    valInt = 11
    valDou = 21.000000
    p = inuxY
    linuxy@linuxy:~/defineDir$ 

    可以看见,PLUSONE(x) 可以接受任何类型的参数 x,这就加大了程序出错的风险。

    问题 2. define 的宏直接替换导致的问题

    宏的替换可以说是字符串的直接替换,所以会导致下面这个问题。

    #include <stdio.h>
    
    #define MULTIPLY(x, y) x * y
    int main() {
        printf("result = %d\n", MULTIPLY(2, 3 + 4));
    }

    输出结果为:

    linuxy@linuxy:~/defineDir$ gcc main.c -o main
    linuxy@linuxy:~/defineDir$ ./main 
    result = 10
    linuxy@linuxy:~/defineDir$ 

    是不是有点惊讶!按照正常的理解输出结果应该为14,但是这里结果却是10,其实程序是这样计算的:

    宏替换后,2 * 3 + 4 = 10,即:直接将 x 替换为 2,y 替换为 3 + 4。而不是将 y 替换为 7。

    为了证明,我们来看下预处理后的程序,执行命令:

    linuxy@linuxy:~/defineDir$ gcc -E -o main.i main.c

    预处理后的 main.i 文件有如下内容:

    ......
    ......
    # 4 "main.c"
    int main() {
        printf("result = %d\n", 2 * 3 + 4);
    }
    ......
    ......

    这下明白了吧!

    那么,如何避免上面这种情况呢 ?

    需要添加括号,规定好优先级,修改后如下所示:

    #include <stdio.h>
    
    #define MULTIPLY(x, y) (x) * (y)
    int main() {
        printf("result = %d\n", MULTIPLY(2, 3 + 4));
    }

    输出结果为:

    linuxy@linuxy:~/defineDir$ gcc main.c -o main
    linuxy@linuxy:~/defineDir$ ./main 
    result = 14
    linuxy@linuxy:~/defineDir$ 

    接下来再说一下 define 的一个容易理解错误的点。

    我们通常意义上的理解是宏定义了就不能更改,事实上真是这样吗?

    我们来看一个简单例子:

    #include <stdio.h>
    
    #define x 5
    int main() {
        printf("x = %d\n", x);
        #undef x
        #define x 10
        printf("x = %d\n", x);
    }

    输出结果为:

    linuxy@linuxy:~/defineDir$ gcc main.c -o main
    linuxy@linuxy:~/defineDir$ ./main 
    x = 5
    x = 10
    linuxy@linuxy:~/defineDir$

    看到了吗?x 值变化了!我们来看一下预处理后的结果,执行命令:

    linuxy@linuxy:~/defineDir$ gcc -E -o main.i main.c 

    文件 main.i 中可以看到源代码部分内容为:

    ......
    ......
    # 4 "main.c"
    int main() {
        printf("x = %d\n", 5);
    
    
        printf("x = %d\n", 10);
    }
    ......
    ......

    x 分别被替换为 5 和 10,是不是很意外!

    接着上面这个问题,再说一下条件编译。

    经常在条件编译中看到 #define 的身影,通常在一个头文件中会有如下定义:

    #ifndef ADD_H
    
    #define ADD_H
    
    //头文件内的语句
    
    
    #endif // ADD_H

    上面语句的意思是:如果还没有定义 ADD_H,就执行 #ifndef ADD_H 后的内容。

    这样可以防止一个头文件被多次包含,重复引用。

    当然,还有其它的条件编译指令,如下所示:

    (1)#if

    检测表达式值是否为真。如果为真,则编译后面的代码直到出现 #else、#elif 或 #endif 为止,否则不编译。类似于 if 语句。

    (2)#else

    当#if #ifdef #ifndef 指令不满足的时候,就执行 #else 后面的代码。

    (3)#elif

    类似于 else if 语句,后面接一个表达式进行判断。

    (4)#ifdef

    判断宏是否定义,如果已定义,则执行后面的语句。

    (5)#ifndef

    判断宏是否未定义,如果未定义,则执行后面的语句。

    (6)#endif

    用于终止 #if #ifdef #ifndef 指令。

    (7)#undef

    用于取消宏的定义,上面已举例说明。

    上面的指令大多和 if else 等语句有类似的功能,但是,上面的指令除 #undef 外,其它指令必须有结束标志 #endif。

    当然,条件编译还有其它的用途,比如:跨平台、调试的情况。

    最后,说一下 # 开头的预处理指令。

    在文件中以?# 开头的预处理指令,都是在预处理环节处理的,上面已经看了宏的预处理,接下来再看一个简单例子:

    #include <stdio.h>
    
    int main() {
        printf("Hello World!");
    }
    

    预处理一下上面的代码,执行命令:

    linuxy@linuxy:~/defineDir$ gcc -E -o main.i main.c

    查看 main.i 文件,部分内容如下所示:

    extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
    # 840 "/usr/include/stdio.h" 3 4
    extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    
    
    
    extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
    
    
    extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    # 858 "/usr/include/stdio.h" 3 4
    extern int __uflow (FILE *);
    extern int __overflow (FILE *, int);
    # 873 "/usr/include/stdio.h" 3 4

    可以看到头文件 #include<stdio.h> 被展开了。

    总结

    好了,define 的知识点就讲解完了,希望对大家有帮助!

    关注专栏:C/C++面试通关集锦?(优质好文持续更新中……)

    cs
    下一篇:没有了