pragma 简介

  • #pragma 用于指示编译器完成一些特定的动作
  • #pragma 所定义的很多指示字是编译器特有
  • #pragma 在不同的编译器间是不可移植

    • 预处理器将忽略它不认识的#pragma指令
    • 不同的编译器可能以不同的方式解析同一条#pragma指令

一般用法:

'#pragma parameter'

注: 不同的 parameter 参数语法和意义各不相同

C 语言预留给编译器厂商的扩展指示字

pragma message

  • message 参数在大多数的编译器中都有相似的实现
  • message 参数在编译时输出消息到编译输出窗口中
  • message 用于条件编译中可提示代码的版本信息
#if defined(ANDROID20)
    #pragma message("Compile Android SDK 2.0 ...")
    #define VERSION "Android 2.0"
#endif
与 #error 和 #warning 不同,#pragma message 仅仅代表一条编译消息,不代表程序错误。

实例分析: #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;
}
编译输出:
[GCC]  test.c:10: note: #pragma message: Compile Android SDK 4.0...
[VC]   Compile Android SDK 4.0...

运行输出:
[GCC]  Android 4.0
[VC]   Android 4.0

pragma once

  • #pragma once 用于保证头文件只被编译一次
  • #pragma once 是编译器相关的,不一定被支持
#ifndef _HEADER_H_
#define _HEADER_H_

// source code

#endif

#pragma once

这两种方式有什么区别呢?

  • #ifndef 为 C 语言支持,实质包含了多次,通过宏来决定是否选择是否嵌入到源代码中,预处理器处理多次,保证只“嵌入一次”。
  • #pragma once 为编译器支持,是真正的只编译一次,之后遇到此文件,直接不做处理。
  • #pragma once 具有更高的编译效率。

示例分析: #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;
输出:
g_value = 1

VC GCC  : 无警告,无错误              【主持】
BCC     : 编译出错, g_value多次定义  【不支持】
  • 兼顾移植性与编译效率的方法
    #ifndef _HEADER_H_
    #define _HEADER_H_
    
    #pragma once
    
    // source code
    
    #endif

pragma pack

  • 什么是内存对齐

    • 不同类型的数据在内存中按照一定的规则排列
    • 而不一定是顺序的一个接一个的排列
struct Test1
{
    char c1;
    short s2;
    char c2;
    int i;
};

struct Test2
{
    char c1;
    char c2;
    short s2;
    int i;
};

Test1 和 Test2 所占的内存空间是否相同?

sizeof(struct Test1) = 12
sizeof(struct Test2) = 8

clipboard.png

为什么需要内存对齐?

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

    • 当读取操作的数据未对齐,则需要两次总线来访问内存,因此性能会大打折扣
    • 某些硬件平台只能从规定的相对地址读取特定类型的数据,否则产生硬件异常
  • #pragm pack 用于指定内存对齐方式

未对齐造成两次内存读取【32位机器的读写最小粒度4字节】
clipboard.png

#pragma pack 能够改变编译器的默认对齐方式

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

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

sizeof(struct Test1) = 8
sizeof(struct Test2) = 8

struct 占用的内存大小

  • 第一个成员起始于 0 偏移处
  • 每个成员按其类型大小和pack参数中较小的一个进行对齐

    • 偏移地址必须能被对齐参数整除
    • 结构体成员的对齐参数大小取其内部pack参数与内部长度最大的数据成员之间较小的作为其对齐参数大小
  • 结构体总长度必须为所有对齐参数的整数倍

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

编程实验: 结构体大小计算

Test_1.c

#include <stdio.h>

#pragma pack(2)
struct Test1
{               // 对齐参数  偏移地址  大小
    char c1;    // 1        0       1
    short s2;   // 2        2       2
    char c2;    // 1        5       2     
    int i;      // 2        6       4
};
#pragma pack()

#pragma pack(4)
struct Test2
{               // 对齐参数  偏移地址  大小
    char c1;    // 1        0       1
    char c2;    // 1        1       1
    short s2;   // 2        2       2
    int i;      // 4        4       4
};
#pragma pack()

int main()
{
    printf("%d\n", sizeof(struct Test1));    
    printf("%d\n", sizeof(struct Test2));    
}
输出:
10
8

Test_2.c

#include <stdio.h>

#pragma pack(8)

struct S1    
{                 // 对齐参数 偏移地址  大小
    short s;      // 2       0       2
    long b;       // 4       4       4
};                // 整体长度为所有对齐参数的整数倍, len = 4 + 4 = 8

struct S2
{                 // 对齐参数  偏移地址 大小
    char c;       // 1        0      1
    struct S1 d;  // 4        4      8
    double e;     // 8        16     8
};                // 整体长度为所有对齐参数的整数倍 , len = 8 + 16 = 24

#pragma pack()

int main()
{
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
}
输出:[GCC] 
8
20   【截至2018/12/04,GCC 暂不支持8字节对齐,忽略pack(8),默认4字节对齐】
 
输出:[VC]
8
24 

小结

  • #pragma 用于指示编译器完成一些特定的动作
  • #pragma 所定义的很多指示字都是编译器特有的

    • #pragma message 用于自定义编译消息
    • #pragma once 用于保证头文件只被编译一次
    • #pragma pack 用于指定内存对齐方式

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧