C语言进阶剖析 24 #pragma 使用分析

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 -DANDROID40 test.c
编译输出:
[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

在这里插入图片描述

为什么需要内存对齐?

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

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

  • #pragm pack 用于指定内存对齐方式

未对齐造成两次内存读取【32位机器的读写最小粒度4字节】
在这里插入图片描述

#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参数中较小的一个进行对齐
        ○ 偏移地址必须能被对齐参数整除
        ○ 结构体成员的整体对齐参数大小取其#pragma 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        4       1      
    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        8      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   【GCC 暂不支持8字节对齐,忽略pack(8),默认4字节对齐】
 
输出:[VC]
8
20


小结

  • #pragma 用于指示编译器完成一些特定的动作
  • #pragma 所定义的很多指示字都是编译器特有的
        ○ #pragma message 用于自定义编译消息
        ○ #pragma once 用于保证头文件只被编译一次
        ○ #pragma pack 用于指定内存对齐方式



发布了334 篇原创文章 · 获赞 661 · 访问量 130万+

猜你喜欢

转载自blog.csdn.net/czg13548930186/article/details/86980147
今日推荐