C进阶养成记 - 养成记24:#pragma使用分析

--事物的难度远远低于对事物的恐惧!

    直接进主题吧,今天来分析下#pragma指示字,该指示字是C语言留给编译器处理的。

    -#pragma用于指示编译器完成一些特定的动作

    -#pragma所定义的很多指示字是编译器特有的

    -#pragma在不同的编译器间是不可移植的

        -预处理器将忽略它不认识的#pragma指令

        -不同的编译器可能以不同的方式解释同一条#pragma指令

一般用法为:#pragma parameter  (其中 不同的parameter参数语法和意义各不相同)

#pragma message

   -message参数在大多数的编译器中都有相似的实现

    -message参数在编译时输出消息到编译输出窗口中

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

VC编译器下编译结果如下,我在命令行中定义了宏ANDROID23,编译过程编译器将符合条件编译的#pragma message信息给打印出来,这样我们就能看到我们所编译的版本信息,gcc编译器下输出格式可能会有所不同,但是功能是类似的。



#pragma once

    -#pragma once 用于保证头文件只被编译一次

    -#pragma once是编译器相关的,不一定支持

想想如下图片中的两种头文件处理方式,有什么不同?



#ifndef...方式属于C语言中条件编译,任何C语言编译环境,都支持该种方式,但是同一个头文件重复包含,最后是通过预处理器去处理重复包含的部分,编译效率比较低下;而#pragma once就直接保证了该头文件只编译一次,效率相对来说会高一些,又由于#pragma once是与编译器相关的,所以工程中的做法一般如下:

#ifndef __FILENAME_H__
#define __FILENAME_H__

#pragma once	//头文件加上这句
		//即可保证支持#pragma once的编译器进行高效编译
		//而对于不支持#pragma once的编译器,则使用#findef...的方式进行编译
//your code ...

#endif

#pragma pack

    在说#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;
}

编译执行结果如下:


从输出结果来看,虽然两个结构体的元素一样,但是所占的内存空间大小不一样,这就涉及内存对齐的情况。

下边是两个结构体在内存中的排布情况


为了分析上图,我们首先来看下为什么需要内存对齐?

    -CPU对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16...字节

    -当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣

    -某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则产生硬件异常

    -#pragma pack用于指定内存对齐方式,使用方式为:#pragma pack(number)  number为对齐字节数

下边我们将上一段代码修改成下边式样,使用#pragma pack(1)使得两个结构体都按1个字节对齐

#include <stdio.h>

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

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

int main()
{
    printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
    printf("sizeof(Test2) = %d\n", sizeof(struct Test2));

    return 0;
}

编译执行:


现在可以看到,两个结构体在内存中所占用的内存大小一致了,现在来重点分析下struct中的内存分配问题

    -struct第一个成员起始于 0偏移处

    -每个成员按其类型大小和pack参数中较小的一个进行对齐

        -偏移地址必须能被对齐参数整除

        -结构体成员的大小取其内部长度最长的数据成员作为其大小

    -结构体总长度必须为所有对齐参数的整数倍

    编译器在默认情况下按照4字节对齐

有了以上struct结构体内存分布原则,我们在来分析下上边最开始的两个结构体(默认4字节对齐),为什么一个占12字节,一个占8字节。

struct Test1
{
		// 对齐参数    偏移地址   占用大小
    char  c1;	//    1	         0	    1
    short s;	//    2	         2	    2
    char  c2;	//    1	         4	    1	
    int   i; 	//    4          8	    4
};
//Test1最后一个成员偏移地址为8,又往后占用了4字节,所以结构体大小为12字节

struct Test2
{
		// 对齐参数    偏移地址    占用大小
    char  c1;	//    1	         0	     1
    char  c2;	//    1	         1	     1
    short s;	//    2          2           2
    int   i;	//    4	         4           4
};
//Test1最后一个成员偏移地址为4,又往后占用了4字节,所以结构体大小为8字节

相信进过以上的分析,对于内存对齐,你已经有了比较清晰的认识,下边来看看一个微软的面试题:

#include <stdio.h>

#pragma pack(8)

struct S1
{
    short a;
    long b;
};

struct S2
{
    char c;
    struct S1 d;    //注:结构体以最大成员的字节数参与选取字节对齐的参数,所以这个的对齐参数为4
    double e;
};

#pragma pack()

int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));

    return 0;
}

这道题有两个地方需要注意:

    1、结构体作为成员时,其参与内存对齐参数的数值为结构体内最大成员的字节数

    2、gcc编译器暂时不支持8字节对齐,所以在gcc环境下,输出分别为8和20,而在vc环境下输出为8和24(大家可自行验证)


总结:

    -#pragma 用于指示编译器完成一些特定的动作

    -#pragma所定义的很多指示字是编译器特有的

        -#pragma message 用于自定义编译消息

        -#pragma once用于保证头文件只被编译一次

        -#pragma pack用于指定内存对齐方式

猜你喜欢

转载自blog.csdn.net/lms1008611/article/details/80141744
今日推荐