乐趣区

关于c:C进阶24pragma分析

Summary

1)#pragma 用于 批示编译器 实现一些特定的动作;#pragma 所定义的 很多批示字是编译器特有的 ,所以在不同的编译器间是 不可移植

  • 预处理器 疏忽 它不意识的 #pragma 指令
  • 不同的编译器可能以不同的形式解释同一条 #pragma 指令
  • 个别用法:#pragma parameter,不同的 parameter 参数语法和意义各不相同。

2)#pragma message在编译时输入信息到编译输入窗口;和 #error、#warning 不同,#pragma message 仅仅示意一条提示信息,不代表谬误。(vc、bcc 和 gcc 三款编译器行为不同)

3)#pragma once用于 保障头文件只被编译一次 #pragma once编译器相干 的,不肯定被反对。(vc 和 gcc 反对,bcc 不反对)

4)#ifndef...#define...#endif也能够用来避免头文件被反复蕴含,和 #pragma once 有何不同?

  • #ifndef形式是 C 语言反对 的,用在各个编译器都能够;如果一个 .h 被 include 了 n 次 预编译器就会判断 n 次 这个头文件是否曾经蕴含了。
  • #pragma once形式是 编译器相干 的,不肯定所有编译器都反对;预编译器只会解决一次,前面不再判断。

5)什么是内存对齐?

  • 不同类型的数据在内存中依照肯定的规定排列 不肯定是程序的 一个接一个的排列

6)为什么须要内存对齐?

  • CPU 对内存的读取不是间断的,而是 分成块读取 的,块的大小只能是 2 的幂,1、2、4、8… 字节
  • 当读取操作的数据未对齐,则须要两次 总线周期 来拜访内存,因而 性能 会大打折扣
  • 某些硬件平台只能从规定的绝对地址处读取特定类型的数据,否则会产生硬件异样

7)编译器默认的对齐形式为 4 字节对齐, #pragma pack(n) 能够调整编译器的默认对齐形式(vc 和 bcc 编译器反对 8 字节对齐,然而 gcc 不反对)

8)内存对齐的规定

·#pragma 剖析

#pragma 用于 批示编译器 实现一些特定的动作;
#pragma 所定义的很多批示字是编译器特有的,所以在不同的编译器间是 不可移植

  • 预处理器 疏忽 它不意识的 #pragma 指令
  • 不同的编译器可能以不同的形式解释同一条 #pragma 指令
  • 个别用法:#pragma parameter,不同的 parameter 参数语法和意义各不相同。

1、#pragma message

  • message 参数在大多数的编译器中都有类似的实现
  • message 参数在编译时输入音讯到编译输入窗口中
  • message 用于条件编译中可提醒代码的版本信息

    #include <stdio.h>
    
    #if defined(ANDROID20)
      #pragma message("Compile Android SDK 2.0...")
      #define VERSION "ANDROID 2.0"
    
    #elif defined(ANDROID30)
      #pragma message("Compile Android SDK 3.0...")
      #define VERSION "Android 3.0"
    
    #elif defined(ANDROID40)
      #pragma message("Compile Android SDK 4.0...")
      #define VERSION "Amdroid 4.0"
    
    #else 
      #error Compile version is not provided!
     
    #endif
    
    int main()
    {printf("%s\n", VERSION);
    
      return 0;
    }
    不同编译器的编译后果:bcc 编译器:bcc32 -DANDROID30 test.c
      编译输入:Compile Android SDK 3.0...
    
      vc 编译器:cl -DANDROID30 test.c
      编译输入:Compile Android SDK 3.0...
    
      gcc 编译器:gcc -DANDROID30 test.c
      编译输入:note: #pragma message: Compile Android SDK 4.0...
    
    三款编译器的输入阐明:#pragma message 会输入提示信息,然而行为类似,可能具体实现不同
    单步编译的两头文件: gcc -DANDROID30 -E test.c -o test.i
    # 12 "test.c"
    #pragma message("Compile Android SDK 3.0...")
    # 12 "test.c"
    # 20 "test.c"
    int main()
    {printf("%s\n", "Android 3.0");
    
      return 0;
    }

    留神:和 #error、#warning 不同,#pragma message仅仅代表一条编译信息,不代表程序谬误。

2、#pragmae once

  • #pragma once用于 保障头文件只被编译一次
  • #pragma once 编译器相干 的,不肯定被反对

#ifndef...#define...#endif也能够用来避免头文件被反复蕴含,和 #pragma once 有何不同?

  • #ifndef形式是 C 语言反对 的,用在各个编译器都能够;如果一个 .h 被 include 了 n 次 预编译器就会判断 n 次 这个头文件是否曾经蕴含了。
  • #pragma once形式是 编译器相干 的,不肯定所有编译器都反对;预编译器只会解决一次,前面不再判断。

    // test.h
    #pragma once
    int aa = 1;
    
    // test.c
    #include <stdio.h>
    #include "test.h"
    #include "test.h"
    
    int main()
    {return 0;}
    不同编译器的编译后果:bcc 编译器:bcc32 test.c
      编译输入:Variable 'aa' is initialized more than once
    
      vc 编译器:cl test.c
      编译输入:胜利
    
      gcc 编译器:gcc test.c
      编译输入:胜利
    
    剖析:#pragma once 预处理批示字,vc 和 gcc 都能够辨认,然而 bcc 就无奈辨认,间接疏忽了,而后 aa 就会被定义 2 次,造成编译谬误

因为 #ifndef 会被判断 n 次,然而#pragma once 又不是所有编译器都反对的,为了 提高效率 ,能够这两种形式 同时应用:

#ifndef _TEST_H_
#define _TEST_H_

#pragma once

// code

#endif

3、#pragma pack 和内存对齐

3.1 什么是内存对齐?

  • 不同类型的数据在内存中依照肯定的规定排列 不肯定是程序的 一个接一个的排列

3.2 为什么须要内存对齐?

  • CPU 对内存的读取不是间断的,而是 分成块读取 的,块的大小只能是 2 的幂,1、2、4、8… 字节
  • 当读取操作的数据未对齐,则须要两次 总线周期 来拜访内存,因而 性能 会大打折扣
  • 某些硬件平台只能从规定的绝对地址处读取特定类型的数据,否则会产生硬件异样

3.3 #pragma pack

编译器默认的对齐形式为 4 字节对齐
#pragama pack 能够调整编译器的默认对齐形式

  • #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,在调整了内存对齐形式后,Test1 占用的内存大小产生了变动。

3.3 struct 内存对齐的规定:

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

    • 偏移地址 必须能被 对齐参数 整除(上一个成员的 offset+size 地位,偏移地址 / 对齐参数 == 0)
    • 构造体类型成员 的类型大小 取其外部长度最大的数据 成员作为其大小
  • struct 的总长度必须为 所有对齐参数的整数倍
#include <stdio.h>

#pragma pack(8)
struct Test1
{
    short a;
    long b;
};

struct Test2
{
    char c;
    struct Test1 st;
    double d;
};
#pragma pack()

int main()
{printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
    
    printf("sizeof(Test2) = %d\n", sizeof(struct Test2));   
    
    return 0;
}
不同编译器下的输入:vc:8 和 24
gcc:8 和 20
bcc:8 和 24

剖析:vc 和 bcc 的输入后果合乎咱们的算法,为什么 gcc 不一样的后果呢?
答案:#pragma定义的 很多编译器批示字是特有的 ,不同编译器间 不可移植 gcc 编译器不反对 8 字节对齐,所以 gcc 编译器看到 ’#pragma pack(8)’ 就间接删掉、疏忽了, 依然依照 4 字节对齐,失去后果为 20:

struct Test2
{                            pack    offset     memberSize
    char c;                    1        0        1
    struct Test1 st;           4        4        8
    double d;                  4        12       8
}                                                    总大小:20

本文总结自“狄泰软件学院”唐佐林老师《C 语言进阶课程》。
如有错漏之处,恳请斧正。

退出移动版