第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 ;
  21. }

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

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


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

  1. # "24-1.c"
  2. # "<built-in>"
  3. # "<command-line>"
  4. # "/usr/include/stdc-predef.h"
  5. # "<command-line>"
  6. # "24-1.c"
  7. # "24-1.c"
  8.  
  9. # "24-1.c"
  10. #pragma message("Compile Android SDK 4.0...")
  11. # "24-1.c"
  12.  
  13. int main()
  14. {
  15.  
  16. return ;
  17. }

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

  1. -.c::: note: #pragma message: Compile Android SDK 4.0...
  2. #pragma message("Compile Android SDK 4.0...")
  3. ^

如果程序中有多个 #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 ;
  10. }

// global.h

  1. #pragma once
  2.  
  3. int g_value = ;

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

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

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

  1. D:\>cl test.c
  2. 用于 80x86 Microsoft (R) C/C++ 优化编译器 15.00.21022.08
  3. 版权所有(C) Microsoft Corporation。保留所有权利。
  4.  
  5. test.c
  6. Microsoft (R) Incremental Linker Version 9.00.21022.08
  7. Copyright (C) Microsoft Corporation. All rights reserved.
  8.  
  9. /out:test.exe
  10. test.obj
  11.  
  12. D:\>test.exe
  13. g_value =

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

  1. D:\>bcc32 test.c
  2. Borland C++ 5.5. for Win32 Copyright (c) , Borland
  3. test.c:
  4. Error E2445 global.h : Variable 'g_value' is initialized more than once // g_value重定义
  5. *** 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 ;
  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 ;
  24. }

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

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

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

VS2010编译器

  1. D:\>cl test.c
  2. 用于 80x86 Microsoft (R) C/C++ 优化编译器 15.00.21022.08
  3. 版权所有(C) Microsoft Corporation。保留所有权利。
  4.  
  5. test.c
  6. Microsoft (R) Incremental Linker Version 9.00.21022.08
  7. Copyright (C) Microsoft Corporation. All rights reserved.
  8.  
  9. /out:test.exe
  10. test.obj
  11.  
  12. D:\>test.exe // 这里和gcc结果不同是因为在该平台下sizeof(long) = 4

BCC32编译器

  1. D:\>bcc32 test.c
  2. Borland C++ 5.5. for Win32 Copyright (c) , Borland
  3. test.c:
  4. Turbo Incremental Link 5.00 Copyright (c) , Borland
  5.  
  6. D:\>test.exe // 这里和gcc结果不同是因为在该平台下sizeof(long) = 4

第24课 - #pragma 使用分析的更多相关文章

  1. 第24课 #pragma使用分析

    #pragma是C语言留给编译器厂商进行扩展用的. 这个关键字在不同的编译器之间也许是不能够移植的. #pragma简介 #pragma message #pragma message打印的消息并不代 ...

  2. 跟我一起学编程—《Scratch编程》第24课:幸运大转盘

    同学你好,欢迎来到<跟我一起学编程>,我是包老师.这是<Scratch3.0编程>课程的第24课,我这节课教你做一个抽奖游戏:幸运大转盘. 学习目标: 1. 能够熟练使用造型工 ...

  3. Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation

    原文:Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

  4. 第50课 C++对象模型分析(上)

    1. 回归本质 (1)class是一种特殊的结构体 ①在内存中class依旧可以看作变量的集合 ②class与struct遵循相同的内存对齐规则 ③class中的成员函数与成员变量是分开存放的.即每个 ...

  5. #pragma使用分析

    #pragma简介 #pragma用于指示编译器完成一些特定的动作 #pragma所定义的很多指示字是编译器特有的 #pragma在不同的编译器间是不可移植的 预处理器将忽略它不认识的#pragma指 ...

  6. 第51课 C++对象模型分析(下)

    1. 单继承对象模型 (1)单一继承 [编程实验]继承对象模型初探 #include <iostream> using namespace std; class Demo { protec ...

  7. [转][Swust OJ 24]--Max Area(画图分析)

    转载自:http://www.cnblogs.com/hate13/p/4160751.html Max Area 题目描述:(链接:http://acm.swust.edu.cn/problem/2 ...

  8. JAVA_SE基础——24.面向对象的内存分析

    黑马程序员入学blog ... 接着上一章的代码: //车类 class Car{ //事物的公共属性使用成员变量描述. String name; //名字的属性 String color; //颜色 ...

  9. 24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)

    1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下: 1.1我们以上图的read id(读ID)为例,它的时序图如下: 首先需要使能CE片选 1)使能CLE 2)发送0X90命 ...

随机推荐

  1. 关于 JavaScript 字符串的一个小知识

    说起字符串,我们再熟悉不过了.接触编程的第一个经典任务就是输出字符串:Hello, world.但是你知道 JavaScript 字符串在计算机里是怎么表示的吗? 最简单直观但不太准确的的理解就是,字 ...

  2. Core + Vue 后台管理基础框架9——统一日志

    1.背景 前阵子有园友留言,提到日志相关的东西,同时,最近圈子里也有提到日志这个东西.一个充分.集中的统一日志平台还是很有必要的,否则系统出问题了只能靠猜或者干瞪眼.何谓充分,日志记录满足最低要求.出 ...

  3. 【数论】莫比乌斯反演Mobius inversion

    本文同步发布于作业部落,若想体验更佳,请点此查看原文.//博客园就是渣,连最基本的符号都打不出来.

  4. Centos7重置root密码(详细版)

    修改了root密码,步骤如下: 步骤一:在开机出现如下界面的时候就按“e”键     步骤二:在步骤一按下”e”键之后,出现如下界面,按 ↓键一直到底部找到“LANG=zh_CN.UTF-8”这句,在 ...

  5. Android app启动出现白屏闪屏

    出现白屏闪屏原因: 进入到AppStartActivity,但是未加载到布局文件,就先显示了窗口的背景,白屏就是显示的windows的背景,即所设置的theme. onCreate()中的setCon ...

  6. linux系统相关参数查询(内存,磁盘,CPU)

    1.服务器型号:dmidecode -s system-product-name 出厂日期:dmidecode -s bios-release-date 2.磁盘大小:parted -l 3.物理内存 ...

  7. SimpleXMLElement::addChild添加同级的空值xml节点

    SimpleXMLElement::addChild添加同级的空值xml节点后,变成了其后面节点的父节点 解决方案:将节点以属性值的方式赋空值,$info->CONV_LEGAL_UNIT_NU ...

  8. 学生成绩管理系统(SSM+MySQL+JSP)

    开发工具:Eclipse前端技术:基础:html+css+JavaScript框架:JQuery+H-ui后端技术:Spring+SpringMVC+mybatis模板引擎:JSP数据库:mysql ...

  9. 【转】Echarts自适应

    var myChart1 = echarts.init(document.getElementById('chart1')); var option = myChart1.getOption(); w ...

  10. File类 -《学堂在线》

    File类的作用 ·创建.删除文件: ·重命名文件:判断文件的读写权限及是否存在: ·设置和查询文件的最近修改时间等: ·构造文件流可以使用File类的对象作为参数. //: FileTester.j ...