C语言深度解剖笔记4之预处理

#define

#define用来定义宏,也可以用来定义字符串,尤其是路径,如C), #define ENG_PATH_3 E:\English\listen_to_this\listen\
有的系统里规定路径的要用双反斜杠“\\”,用define 宏定义表达式

#undef:#undef 是用来撤销宏定义的 用法:

#define PI 3.141592654

// code

#undef PI

//下面的代码就不能用PI 了,它已经被撤销了宏定义。

看下面的题目:

#define X 3
#define Y X*2
#undef X
#define X 2
int z=Y;求z,答案是4,因为X被撤销了宏定义,而Y没有,又X被重新定义,所以Y=2*2.

条件编译:条件编译的功能使得我们可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的,下面介绍三种形式:

1.第一种形式:
#ifdef 标识符
程序段1
#else
程序段2
#endif,本格式中的#else 可以没有。

第二种形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif

第三种形式:
#if 常量表达式
程序段1
#else
程序段2
#endif

此外还有一个#elif

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

#include<filename>和#include"filename",第二个双引号需要注意的是预处理应在当前目录中查找文件名为
filename 的文件,若没有找到,则按系统指定的路径信息,搜索其他目录。

#error 预处理指令的作用是,编译程序时,只要遇到#error 就会生成一个编译错误提
示消息,并停止编译。其语法格式为:
#error error-message
注意,宏串error-message 不用双引号包围。遇到#error 指令时,错误信息被显示,如

#ifdef XX
...
#error XX has been defined

#else

#endif

#pragma 预处理:它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作

其格式一般为:#pragma para,其中para 为参数,下面来看一些常用的参数

#pragma message("消息文本"),当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

假设我们希望判断自己有没有在源代码的什么地方定义了_X86 这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif

如果我们定义了_X86,应用程序在"编译时"就会在编译输出窗口里显示“_X86 macro activated!”

#pragma once,这条指令能够保证头文件被编译一次

]#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译

#pragma warning:最常见的用法及时#pragma warning(disable:4996)

#pragma pack:这是一个与内存对齐有关的预处理。

使用指令#pragma pack (n),编译器将按照n 个字节对齐。

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

接下来我们来仔细讨论下内存对齐。

内存对齐的原因

1.硬件平台问题:某些硬件平台只能在某些地址处取某些特定类型的数据。否则会抛出异常

2.性能问题:数据结构(尤其是栈)应该尽可能地在自然边界上对齐为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

内存对齐问题,主要存在于struct和union等复合结构在内存中的分布

struct A{
    char a;
    int b;
    short c;
};
 
struct B{
    short c;
    char a;
    int b;
};

两个结构体中的成员是一样的,只是分布的先后顺序不同,我们可以试一下输出sizeof(A)和sizeof(B),得到的结构是不一样的,前者是12,后者是8.,这就是因为内存对齐导致的结果不同。

如果想得知结构体的某个特定成员在结构体的位置,则使用offsetof宏(定义于头文件stddef.h)。

内存对齐规则:

对于结构中的第一个成员位于偏移量(offsetof)为0的地址位置,

以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。即min($pragma pack,数据成员类型的大小),下面我们把这个叫做实际对齐数。

最后整体的结构体或者联合体也要对齐,按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。 min(#pragram pack() , 长度最长的数据成员);

当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

Windows中默认对齐数为8,Linux中默认对齐数为4;vc++6.0默认是8

我们来分析下上面的代码。

struct A{
    char a;
    int b;
    short c;
};

char a是一个字节。min(sizeof(char),8)=1,所以第一个内存块是0,接着是b,b的偏移量是1,int是4个字节,min(sizeof(int),8)=4,1(偏移量)不是4的整数倍,所以,应该把b的偏移量应该为4,即在a的后面再添加3个字节,所以b所占的内存块是4-8,接着是short c,short是2个字节。min(sizeof(short),8)=2,c的偏移量是8,8是2的整数倍。所以不用改变。此时一共占了10个字节,结构体的对齐, min(8,4)=4,所以总体的大小应该是4的倍数,所以还需要添加两个字节。

如图

struct B{
    short c;
    char a;
    int b;
};

这是第二个结构体,与第一个的结构体是数据成员顺序的不同,short是2个字节,所以c的内存块是0-1,接着是a,那么a的偏移量是2,而min(8,1)=1,2是1的整数倍数,所以a的内存块是2,接着是b,min(8,4)=4,b的偏移量是3,3不是4的整数倍数,所以需要扩充到4,所以需要在a的后面再添加一个字节(这个字节编号是3)所以b的内存块是(4-7),接着是结构体的对齐,min(8,4)=4,而整体的大小是8,8是4的倍数,所以不需要添加了。

如图:

cpu在读取内存时是一块一块读取的,这就是上面说的内存块。

# 这也是一个运算符。它可以把语言符号转化为字符串。使用方法是“#+需要替换的语言符号”举个例子

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

SQR(8);//输出为:The square of x is 64

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

SQR(8);//输出的是:The square of 8 is 64.

##:这个运算符把两个语言符号组合成单个语言符号,就是个粘合剂,将前后两部分粘合起来

#define XNAME(n) x ## n

XNAME(8)//输出是x8。

猜你喜欢

转载自blog.csdn.net/woainilixuhao/article/details/86552192
今日推荐