第24课 - #pragma 使用分析

第24课 - #pragma 使用分析

1. #pragma简介

(1)#pragma 是一条预处理器指令

(2)#pragma 指令比较依赖于具体的编译器,在不同的编译器之间不具有可移植性,表现为两点:

          ① 编译器A支持的 #pragma 指令在编译器B中也许并不支持,如果编译器B碰到这条不认识的指令就会忽略它。比如下文中介绍的 #pragma once指令,gcc编译器VS编译器是支持的,但bcc编译器就不支持。

          ② 同一条 #pragma指令,不同的编译器可能会有不同的解读。

(3)一般用法:#pragma parameter     // 注意,不同的 parameter参数 语法和含义是不同的

2. #pragma message指令

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

(2)message参数在编译时输出消息到编译输出窗口中

(3)message用于条件编译可提示代码的版本信息

(4)与 #error 和 #warning不同,#pragma message仅仅代表一条编译消息,不代表程序错误。

【#pragma使用示例】

 1 #include <stdio.h>
 2 
 3 #if defined(ANDROID20)
 4     #pragma message("Compile Android SDK 2.0...")
 5     #define VERSION "Android 2.0"
 6 #elif defined(ANDROID23)
 7     #pragma message("Compile Android SDK 2.3...")
 8     #define VERSION "Android 2.3"
 9 #elif defined(ANDROID40)
10     #pragma message("Compile Android SDK 4.0...")
11     #define VERSION "Android 4.0"
12 #else
13     #error Compile Version is not provided!
14 #endif
15 
16 int main()
17 {
18     printf("%s\n", VERSION);
19 
20     return 0;
21 }

使用 gcc 编译并观察输出结果

  

使用VS2010的编译器BCC编译器分别对上述的示例代码进行编译,可以看到结果和gcc编译器的稍有不同,这也验证了上面说的,不同的编译器对同一条 #pragma 指令会有不同的解读。

     

  


 使用 gcc -E 24-1.c -DANDROID40 编译代码,发现 #pragma message 并不是在预处理的时候输出的。

# 1 "24-1.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "24-1.c"
# 10 "24-1.c"
            
# 10 "24-1.c"
#pragma message("Compile Android SDK 4.0...")
# 10 "24-1.c"

int main()
{

    return 0;
}

此时使用 gcc -S 24-1.c -DANDROID40 编译代码,发现编译报错,说明#pragma message是由编译器(狭义)输出的。

24-1.c:10:13: note: #pragma message: Compile Android SDK 4.0...
     #pragma message("Compile Android SDK 4.0...")
             ^

如果程序中有多个 #pragma message,由于编译器对每个c文件是自上而下编译的,所以会自上而下输出。

在做上面这个测试时,很疑惑为什么 #pragma经过预处理器处理后是原样输出,这样为啥还叫预处理指令?

咨询了唐老师,其实是自己钻了牛角尖,这里预处理器的处理方式就是将#pragma原封不动的交给编译器(狭义),不能机械的认为预处理指令完全要预处理器处理。

3. #pragma once指令

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

(2)#pragma once是编译器相关的,不一定被支持(下面的示例程序,gcc编译器和VS2010编译器可以编译通过,但BCC32编译器却编译失败

(3)在第22课分析条件编译时,我们介绍了使用条件编译来防止头文件被多次包含。那 #pragma once 和条件编译有什么区别呢?

      参考博客:https://www.hhcycj.com/post/item/383.html (博客截图)

       

 // test.c

 1 #include <stdio.h>
 2 #include "global.h"
 3 #include "global.h"
 4 
 5 int main()
 6 {
 7     printf("g_value = %d\n", g_value);
 8 
 9     return 0;
10 }

// global.h

1 #pragma once
2 
3 int g_value = 1;

使用 gcc 编译    ==>  编译通过

swj@ubuntu:~/c_course/ch_24$ gcc test.c 
swj@ubuntu:~/c_course/ch_24$ ./a.out 
g_value = 1

使用 VS2010 编译   ==>   编译通过

D:\>cl test.c
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 15.00.21022.08 版
版权所有(C) Microsoft Corporation。保留所有权利。

test.c
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

D:\>test.exe
g_value = 1

使用 BCC32 编译    ==>   编译失败

D:\>bcc32 test.c
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
test.c:
Error E2445 global.h 4: Variable 'g_value' is initialized more than once          // g_value重定义
*** 1 errors in Compile ***

BCC32编译器不支持 #pragma once,遇到 #pragma once之后直接忽略它。

在实际工程中,如果既想有效率又想有移植性,那怎么做呢?一般使用如下的做法。

1 #pragma once
2 
3 ifndef  _HEADER_FILE_H_
4 #define _HEADER_FILE_H_
5 
6 // source code
7 
8 #endif

4. #pragma pack指令

(1)什么是内存对齐?

        不同类型的数据在内存中按照一定的规则排列,而不一定是顺序的一个接一个的排列。

        我们看下面这个例子,struct Test1 和 struct Test2 的成员都是相同的,只是在结构体中的位置不同,那两个结构体占用的内存大小相同吗?

 1 #include <stdio.h>
 2 
 3 #pragma pack(2)
 4 struct Test1
 5 {
 6     char  c1;
 7     short s;
 8     char  c2;
 9     int   i; 
10 };
11 #pragma pack()
12 
13 #pragma pack(4)
14 struct Test2
15 {
16     char  c1;
17     char  c2;
18     short s;
19     int   i;
20 };
21 #pragma pack()
22 
23 int main()
24 
25 {
26     printf("sizeof(Test1) = %zu\n", sizeof(struct Test1));
27     printf("sizeof(Test2) = %zu\n", sizeof(struct Test2));
28 
29     return 0;
30 }

        程序的输出结果如下,可见两个结构体的大小并不相同!!!

         

(2)为什么需要内存对齐?

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

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

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

(3)#pragma pack( )的功能

        #pragma pack( ) 可以改变编译器的默认对齐方式(编译器默认为4字节对齐


 下面我们介绍结构体内存对齐的规则(重要!重要!重要!

  • 第一个成员起始于 0偏移处
  • 对齐参数:每个结构体成员按照 其类型大小pack参数 中较小的一个进行对齐(如果该成员也为结构体,那就取其内部长度最大的数据成员作为其大小)
  • 偏移地址必须能够被对齐参数整除 (0可以被任何非0的整数整除)
  • 结构体总长度必须为所有对齐参数的整数倍

我们根据这个规则来分析一下前面 struct Test1 和 struct Test2 结构体

 1 #pragma pack(2) // 以2字节对齐
 2 struct Test1
 3 {               // 对齐参数    偏移地址    大小
 4     char  c1;   // 1          0         1  
 5     short s;    // 2          2         2
 6     char  c2;   // 1          4         1
 7     int   i;    // 2          6         4 
 8 };              // 在2字节对齐下,该结构体大小为10字节
 9 #pragma pack()
10 
11 #pragma pack(4) // 以4字节对齐
12 struct Test2
13 {               // 对齐参数    偏移地址    大小
14     char  c1;   // 1          0          1
15     char  c2;   // 1          1          1
16     short s;    // 2          2          2
17     int   i;    // 4          4          4
18 };              // 在4字节对齐下,该结构体大小为8字节
19 #pragma pack()

分析结果和前面程序的输出结果相同,结构体成员在内存中的位置如下图所示:

    

上面这个例子比较简单,我们再来看一下微软的一道笔试题

 1 #include <stdio.h>
 2 
 3 #pragma pack(8)    // 以8字节对齐
 4 struct S1
 5 {                  // 对齐参数    偏移地址    大小
 6     short a;       // 2          0          2
 7     long b;        // 8          8          8
 8 };                 // 在8字节对齐下,该结构体大小为16字节
 9 
10 struct S2          // 结构体中包含了一个结构体成员,取其内部长度最大的数据成员作为其大小
11 {                  // 对齐参数    偏移地址    大小
12     char c;        // 1          0          1
13     struct S1 d;   // 8          8          16
14     double e;      // 8          24         8 
15 };                 // 在8字节对齐下,该结构体大小为32字节
16 #pragma pack()
17 
18 int main()
19 {
20     printf("%d\n", sizeof(struct S1));
21     printf("%d\n", sizeof(struct S2));
22 
23     return 0;
24 }

使用gcc编译,程序执行结果如下,和我们分析的结果相同

 

【这里和唐老师课程中的结果不同,唐老师使用的编译器不支持8字节对齐,即 #pragma pack(8),我的这个gcc支持。】

我们再使用 VS2010编译器BCC32编译器 测试一下上面的代码

VS2010编译器

D:\>cl test.c
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 15.00.21022.08 版
版权所有(C) Microsoft Corporation。保留所有权利。

test.c
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

D:\>test.exe             // 这里和gcc结果不同是因为在该平台下sizeof(long) = 4
8
24

BCC32编译器

D:\>bcc32 test.c
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
test.c:
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

D:\>test.exe               // 这里和gcc结果不同是因为在该平台下sizeof(long) = 4
8
24

 

posted @ 2019-11-13 22:57  Hengs  阅读(540)  评论(0编辑  收藏  举报