c语言pragma,C之 #pragma(二十二)

我们今天来介绍下 C 语言中的 #pragma,#pragma 用于指示编译器完成一些特定的动作。#pragma 所定义的很多指示字是编译器特有的,在不同的编译器间是不可移植的。

预处理期将忽略它不认识的

f9e9df1aea042bfdf5838a2f68414145.png#pragma 指令,不同的编译器可能以不同的方式解释同一条 #pragma 指令。一般用法:#pragma parameter。注意:不同的 parameter 参数语法和意义各不相同!

#pragma message:a> message 参数在大多数的编译器中都有相似的实现;b> message 参数在编译时输出消息到编译输出窗口中;c> message 用于条件编译中可提示代码的版本信息。它与#error 和#warning 不同,#pragma message仅仅代表一条编译消息,不代表程序错误。

下来我们来分析个示例代码,代码如下#include 

#if defined(ANDROID20)

#pragma message("Compile Android SDK 2.0...")

#define VERSION "Android 2.0"

#elif defined(ANDROID23)

#pragma message("Compile Android SDK 2.3...")

#define VERSION "Android 2.3"

#elif defined(ANDROID40)

#pragma message("Compile Android SDK 4.0...")

#define VERSION "Android 4.0"

#else

#error Compile Version is not provided!

#endif

int main()

{

printf("%s\n", VERSION);

return 0;

}

这段代码是想利用 #pragma message 定义一条输出信息,我们来看看在 gcc 编译器中输出什么

513dbe8b4cb529e3aaf213a4f58e9262.png

我们可以看出第一次没有定义 ANDROID 参数,编译直接报我们提示的错误。那么它在输出信息的时候,也同样将 #pragma message 输出了。我们再在 BCC 编译器中编译下,看看输出是什么

5efbc92c1f93ef46dfd094ec4e8cd0b9.png

那么在 BCC 编译器中我们将参数写在后面它还不识别,它编译之后的结果是没有 #pragma message,直接显示后面的信息。由此我们可以看到在不同的编译器中,对 message 的处理结果不一样。

下来我们再讲讲 #pragma once,它是用于保证头文件只被编译一次。那么问题来了,我们之前讲讲 #ifndef xxx_h_     #define xxx_h_     #endif 这种用法。它们两个有什么区别呢?因为后一种的实现是基于宏参数实现的,所以它每次包含到头文件时便会进去检查,所以效率不高。但是因为是宏参数,所以这是 C 语言支持的,在每个编译器中都会识别。#pragma once  是编译器相关的,它不一定被支持。但是因为编译器一看见它就不去包含了,所以它的效率及其高,因此两种方式各有利弊。我们下来做个试验分析下

test.c#include 

#include "global.h"

#include "global.h"

int main()

{

printf("g_value = %d\n", g_value);

return 0;

}

global.h#pragma once

int g_value = 1;

我们在 gcc 编译器中编译下,看看结果如何

07ac111606a74c611609511d399053fd.png

我们看到在 gcc 中没报错误,直接运行。我们下来在 BCC 中再来编译下

9b9a1fa43c38923b38354c16093375c0.png

我们看到在 BCC 中直接报错,显然在 BCC 编译器中就不支持这种写法。

那么下来我们再来看看 C 语言中的内存对齐,什么是内存对齐呢?不同类型的数据在内存中按照一定规则排列,但是不一定是按照顺序的一个接一个的排列。那么为什么需要内存对齐呢?原因有这么几点:1、CPU 对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16 ... 字节;2、当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣;3、某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则会产生异常。#pragma pack 用于指定内存对齐方式。

下面我们来看个示例代码,代码如下#include 

struct Test1

{

char  c1;

short s;

char  c2;

int   i;

};

struct Test2

{

char  c1;

char  c2;

short s;

int   i;

};

int main()

{

printf("sizeof(Test1) = %d\n", sizeof(struct Test1));

printf("sizeof(Test2) = %d\n", sizeof(struct Test2));

return 0;

}

我们看到两个结构体中的成员变量都一样,但是位置不同。那么问题来了,它们所占的内存大小相同吗?我们来看看编译结果

beecf40bcc8e3b3b5552e98d3435a2ad.png

很明显结果不一样,那么为什么呢?这就是内存对齐了,在计算机内部,默认的是4字节对齐,因为这样效率最高。我们可以指定它们都是1字节对齐,这样结果就一样了。#pragma pack 能够改变编译器的默认对齐方式,下面给个示例#pragma pack(1)

struct Test1

{

char  c1;

short s;

char  c2;

int   i;

};

#pragma pack()

分别在两个结构的前后都这样设置按照 1 字节对齐的方式,我们再来看看结果如何

6fe2149ed396d94ab296f96c4a2afdbf.png

这下它们占的内存大小就一样了。之前的那种内存分布如下图所示

f43e52f1e9034359b6f64fef6802e2a0.png

那么 struct 占用的内存大小是怎样计算的呢?第一个成员起始于 0 偏移处,每个成员按其类型大小和 pack 参数中较小的一个进行对齐;偏移地址必须能被对齐参数整除,结构体成员的大小取其内部长度最大的数据成员作为其大小;结构体总长度必须为所有对齐参数的整数倍。编译器在默认情况下按照 4 字节对齐。

下来我们来分析下之前的程序中结构体默认的按照 4 字节怎样对齐的#pragma pack(4)

struct Test1

{                // 内存对齐     起始距离      内存大小

char  c1;    // 1            0            1

short s;     // 2            2            2

char  c2;    // 1            4            1

int   i;     // 4            8            4

};

#pragma pack()

#pragma pack(4)

struct Test2

{                // 内存对齐     起始距离      内存大小

char  c1;    // 1            0            1

char  c2;    // 1            1            1

short s;     // 2            2            2

int   i;     // 4            4            4

};

#pragma pack()

那么大家和上面的表对照下,是不是一样呢。下来我们再做个实验,按照8字节对齐试试,代码如下#include 

#pragma pack(8)

struct S1

{                // 内存对齐     起始距离      内存大小

short a;     // 2            0            2

long b;      // 4            4            4

};

struct S2

{               // 内存对齐     起始距离      内存大小

char c;     // 1            0            1

struct S1 d;// 4            4            8

double e;   // 8            16           8

};

#pragma pack()

int main()

{

printf("%d\n", sizeof(struct S1));

printf("%d\n", sizeof(struct S2));

return 0;

}

按照我们的分析,上面应该分别打印出 8 和 24。我们来看看 gcc 编译器的结果

d82e07e396b59e4308da66f96ffb5c8c.png

我们看到打印出的是 8 和 20,那么我们是不是分析出错了?别着急哈,再看看 BCC 编译器的结果

a0d918a7fa1c5dc9949e0efc6a870d5d.png

我们看到 BCC 编译器和我们分析的一致。那么 gcc 为什么会打印 20 呢?原来在 gcc 编译器中不支持 8 字节对齐的方式,因此它是按照 4 字节对齐方式进行打印的。那么最后一个 double 的起始距离就是 12 了,因此结果就是 20 啦。

通过对 #pragma 的学习,总结如下:1、#pragma 用于指示编译器完成一些特定的动作;2、它所定义的很多指示字是编译器特有的;3、#pragma message 用于 自定义编译消息、#pragma once 用于保证头文件只被编译一次、#pragma pack 用于指示内存对齐方式。

欢迎大家一起来学习C 语言,可以加我QQ:243343083。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值