《c语言深度剖析》整理--预处理

 

一、宏定义

     1. 宏定义的作用域

        宏定义开始,到文件结束(其他的文件包含宏定义的文件也可引用)。

     2. 宏定义可增加代码的可读性

         #define ERROR_POWEROFF -1

         若不采用宏定义的方式,代码中出现-1 时,程序的可读性变差,代码中出现有具体的含义的单独的数字(比如上面-1) 称为魔鬼数,别人阅读代码的时候会抓狂,恐怕自己阅读的时候,也不知具体的含义

     3. 宏预编译阶段宏定义替换代码中具体的定义

        这点代码中定义比较容易出错,比如

        (1)#define SQR (x)   x * x

                  当表达式 x = 10+1, SQR(x) * SQR(x)   替换为 10+1*10+1,显然这不是我们想要的结果,导致出错

        (2)#define ADD (x)  (x)+(x)

                  当表达式 x=5, ADD(x)*ADD(x) 替换为 (5)+(5)*(5)+5,显然这不是我们想要的结果,导致出错

         解析:避免这种错误,当宏定义的时候,每部分都加上括号:(1))#define SQR (x)    ((x) *( x)) (2)#define ADD (x)   ((x)+(x))

       4. 当宏出现在字符串中的时候,宏不会被替换

           比如 printf("ADD(x)");  打印的结果为 ADD(x) 而不是 (x)+(x)

       5. 宏定义函数的时候,函数标识符和参数之间不能有空格

          比如   #define SQR   (x)   x * x   , 宏将变成代码中用(x)   x * x   替换代码中的SQR  ;

         但引用宏的时候可以有空格,比如 #define ADD (x)   ((x)+(x)), 应用的时候 ADD       (3)  和 ADD(3) 都是正确的

        6. 取消宏定义的符号 #undef,此符号之后的宏的定义将不再起作用

 二、条件编译

        条件编译的形式之一:

      (1)     #ifdef 标识符
                              程序段1
                      #else
                             程序段2
                      #endif

      (2)    #if 常量表达式
                              程序段1
                    #else
                             程序段2
                    #endif

         条件编译应用于场景之一,比如当在windows平台下编写代码调试,而程序又是可以运行在linux或者aix平台下,这样可以进行条件编译;

         条件编译和 if--else if---else if .....else 语句一样可以有不同的引申

三、文件包含#include   

       1.  文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。C语言提供#include 命令来实现文件包含的操作,它实际是宏定义的延伸;

        (1)#include <filename>

                  C 编译系统所提供的并存放在指定的子目录下的头文件。找到文件后,用文件内容替换该语句;

       (2)#include “filename”

                 预处理应在当前目录中查找文件名为filename 的文件,若没有找到,则按系统指定的路径信息,搜索其他目录。找到文件后,用文件内容替换该语句。

         #include 是将已存在文件的内容嵌入到当前文件中

      2. #include 支持相对路径

          比如,#include "./filename" (当前目录下的文件filename) #include "./icp/filename"(上层路径文件icp目录下的文件filename)

四、#pragma comment(...)

         该指令将一个注释记录放入一个对象文件或可执行文件中。常用的lib 关键字,可以帮我们连入一个库文件。比如:

         #pragma comment(lib, "user32.lib")

        该指令用来将user32.lib 库文件加入到本工程中。

        linker:将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或 者在开发环境中设置的链接选项,你可以指定/include 选项来强制包含某个对象,

        例如:#pragma comment(linker, "/include:__mySymbol")

五、#pragma warning

       #pragma warning( disable : 4507 34; once : 4385; error : 164 )

      等价于:

      #pragma warning(disable:4507 34) // 不显示4507 和34 号警告信息

      #pragma warning(once:4385) // 4385 号警告信息仅报告一次

      #pragma warning(error:164) // 把164 号警告信息作为一个错误。

      不过程序设计的时候,尽量少用disable,尽量在编码的时候,将warning问题解决掉,有的时候warning 也是潜在的bug

六、#pragma once

       在头文件的最开始加入这条指令就能够保证头文件被编译一次

       另外保证头文件只编译一次的方法: 

       #ifndef  _FILENAME_H

       #define _FILENAME_H

       #endif

七、#pragma code_seg

       另一个使用得比较多的pragma 参数是code_seg。格式如:

       #pragma code_seg( ["section-name"[,"section-class"] ] )

      它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它

八、#pragma message

        能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:

       #pragma message(“消息文本")

       当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 这对于我们进行源码控制,代码调试有帮助。

九、内存对齐 #pragma pack

        1. 内存对齐的原理:

          字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4 整除的地址,和可以被8 整除的地址)无论如

何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存

访问仅需要一次访问。减少内存地址总线访问未对齐的地址的周期。

          2. 内存对齐例子

          (1)struct TestStruct1
                   {
                           char c1;
                           short s;
                           char c2;
                           int i;
                    };

            解析:此结构体在内存中的布局为  1*,11,1*******,1111    (1  代表占用内存,* 代表为内存对齐补的内存空间)

           所以 sizeof(TestStruct1)  为12

         3. 内存对齐的避免

           可以在程序设计的时候,尽量避免内存对齐的情况,尽量自然对齐节省占用内存空间,比如上例

           struct TestStruct2
           {
                   char c1;
                   char c2;
                   short s;
                   int i;
            };

          sizeof(TestStruct2) 为 8

         4. #pragma pack

          #pragma pack (n),编译器将按照n 个字节对齐

          #pragma pack (),编译器将取消自定义字节对齐方式

          例如

           #pragma pack(8)
           struct TestStruct4
           {
                     char a;
                     long b;
           };
          struct TestStruct5
          {
                    char c;
                    TestStruct4 d;
                    long long e;
           };
         #pragma pack()

        解析:

        TestStruct4 内存布局: 1***,1111

        TestStruct5 内存布局:  1***,1***,1111****,11111111

        所以 sizeof(TestStruct4) 为 8,sizeof(TestStruct5)为 24

       5. 内存对齐的规则

         (1)每个成员分别按自己的方式对齐,并能最小化长度

         (2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度

         (3)对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

         (4)对于数组,比如:char a[3];它的对齐方式和分别写3 个char 是一样的.也就是说它还是按1 个字节对齐,即数组按照数组中的每个成员的类型对齐

                   如果写: typedef char Array3[3];Array3 这种类型的对齐方式还是按1 个字节对齐,而不是按它的长度

         (5)不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个

十、#预算符

       1. # 符号的用处之一: 将宏中字符串中的变量,以变量值的形式打印

          (1)比如:

          #define SQR(x) printf("The square of x is %d.\n", ((x)*(x)));

         如果这样使用宏:SQR(8);

         则输出为:

         The square of x is 64.

         (2) 修改上面的宏为:

          #define SQR(x) printf("The square of "#x" is %d.\n", ((x)*(x)));

         再使用:

         SQR(8);

         则输出的是:

        The square of 8 is 64.

十一、##预算符

          ##预算符作用:粘合剂

         #define DISPLAY(n) x ## n

         如果这样使用宏:

         DISPLAY(8)

        则会被展开成这样:

        x8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值