C之 #pragma(二十二)

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

        预处理期将忽略它不认识的 #pragma 指令,不一样的编译器可能以不一样的方式解释同一条 #pragma 指令。通常用法:#pragma parameter注意:不一样的 parameter 参数语法和意义各不相同!性能

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

        下来咱们来分析个示例代码,代码以下spa

#include <stdio.h>

#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 编译器中输出什么图片.png3d

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

图片.png

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

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

test.c
编译器

#include <stdio.h>
#include "global.h"
#include "global.h"

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

    return 0;
}

global.hit

#pragma once

int g_value = 1;

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

图片.png

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

图片.png

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

        那么下来咱们再来看看 C 语言中的内存对齐,什么是内存对齐呢?不一样类型的数据在内存中按照必定规则排列,可是不必定是按照顺序的一个接一个的排列。那么为何须要内存对齐呢?缘由有这么几点:一、CPU 对内存的读取不是连续的,而是分红块读取的,块的大小只能是一、二、四、八、16 ... 字节;二、当读取操做的数据未对齐,则须要两次总线周期来访问内存,所以性能会大打折扣;三、某些硬件平台只能从规定的相对地址处读取特定类型的数据,不然会产生异常。#pragma pack 用于指定内存对齐方式

        下面咱们来看个示例代码,代码以下

#include <stdio.h>

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;
}

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

图片.png

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

#pragma pack(1)
struct Test1
{
    char  c1;
    short s;
    char  c2;
    int   i; 
};
#pragma pack()

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

图片.png

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

图片.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 <stdio.h>

#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 编译器的结果

图片.png

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

图片.png

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

        经过对 #pragma 的学习,总结以下:一、#pragma 用于指示编译器完成一些特定的动做;二、它所定义的不少指示字是编译器特有的;三、#pragma message 用于 自定义编译消息、#pragma once 用于保证头文件只被编译一次、#pragma pack 用于指示内存对齐方式。

        

        欢迎你们一块儿来学习 C 语言,能够加我QQ:243343083

相关文章
相关标签/搜索