C之 #pragma(二十二)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C之 #pragma(二十二)相关的知识,希望对你有一定的参考价值。

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

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

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

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

#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 编译器中输出什么技术分享图片

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

技术分享图片

        那么在 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.h

#pragma once

int g_value = 1;

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

技术分享图片

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

技术分享图片

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

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

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

技术分享图片

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

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

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

技术分享图片

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

技术分享图片

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

技术分享图片

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

技术分享图片

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

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

        

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

以上是关于C之 #pragma(二十二)的主要内容,如果未能解决你的问题,请参考以下文章

BetaFlight模块设计之二十二:地面测距任务分析

Hadoop MapReduce编程 API入门系列之网页流量版本1(二十二)

《C#零基础入门之百识百例》(二十二)数组排序 -- 冒泡排序

Python开发第二十二篇:Web框架之Django进阶

C语言试题二十二之定义了3×3的二维数组,并在主函数中赋值。函数的功能使求出数组周边元素的平均值并作为函数值返回给主函数中的s。

C语言试题二十二之定义了3×3的二维数组,并在主函数中赋值。函数的功能使求出数组周边元素的平均值并作为函数值返回给主函数中的s。