第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使用示例】
#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 ;
}
使用 gcc 编译并观察输出结果

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


使用 gcc -E 24-1.c -DANDROID40 编译代码,发现 #pragma message 并不是在预处理的时候输出的。
# "24-1.c"
# "<built-in>"
# "<command-line>"
# "/usr/include/stdc-predef.h"
# "<command-line>"
# "24-1.c"
# "24-1.c" # "24-1.c"
#pragma message("Compile Android SDK 4.0...")
# "24-1.c" int main()
{ return ;
}
此时使用 gcc -S 24-1.c -DANDROID40 编译代码,发现编译报错,说明#pragma message是由编译器(狭义)输出的。
-.c::: 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
#include <stdio.h>
#include "global.h"
#include "global.h" int main()
{
printf("g_value = %d\n", g_value); return ;
}
// global.h
#pragma once int g_value = ;
使用 gcc 编译 ==> 编译通过
swj@ubuntu:~/c_course/ch_24$ gcc test.c
swj@ubuntu:~/c_course/ch_24$ ./a.out
g_value =
使用 VS2010 编译 ==> 编译通过
D:\>cl test.c
用于 80x86 的 Microsoft (R) 位 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 =
使用 BCC32 编译 ==> 编译失败
D:\>bcc32 test.c
Borland C++ 5.5. for Win32 Copyright (c) , Borland
test.c:
Error E2445 global.h : Variable 'g_value' is initialized more than once // g_value重定义
*** errors in Compile ***
BCC32编译器不支持 #pragma once,遇到 #pragma once之后直接忽略它。
在实际工程中,如果既想有效率又想有移植性,那怎么做呢?一般使用如下的做法。
#pragma once ifndef _HEADER_FILE_H_
#define _HEADER_FILE_H_ // source code #endif
4. #pragma pack指令
(1)什么是内存对齐?
不同类型的数据在内存中按照一定的规则排列,而不一定是顺序的一个接一个的排列。
我们看下面这个例子,struct Test1 和 struct Test2 的成员都是相同的,只是在结构体中的位置不同,那两个结构体占用的内存大小相同吗?
#include <stdio.h> #pragma pack(2)
struct Test1
{
char c1;
short s;
char c2;
int i;
};
#pragma pack() #pragma pack(4)
struct Test2
{
char c1;
char c2;
short s;
int i;
};
#pragma pack() int main() {
printf("sizeof(Test1) = %zu\n", sizeof(struct Test1));
printf("sizeof(Test2) = %zu\n", sizeof(struct Test2)); return ;
}
程序的输出结果如下,可见两个结构体的大小并不相同!!!

(2)为什么需要内存对齐?
① CPU对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16...字节
② 当读取操作的数据未对齐,则需要两次总线周期来访问内存,此性能会大打折扣
③ 某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则产生硬件异常
(3)#pragma pack( )的功能
#pragma pack( ) 可以改变编译器的默认对齐方式(编译器默认为4字节对齐)
下面我们介绍结构体内存对齐的规则(重要!重要!重要!)
- 第一个成员起始于 0偏移处
- 对齐参数:每个结构体成员按照 其类型大小 和 pack参数 中较小的一个进行对齐(如果该成员也为结构体,那就取其内部长度最大的数据成员作为其大小)
- 偏移地址必须能够被对齐参数整除 (0可以被任何非0的整数整除)
- 结构体总长度必须为所有对齐参数的整数倍
我们根据这个规则来分析一下前面 struct Test1 和 struct Test2 结构体
#pragma pack(2) // 以2字节对齐
struct Test1
{ // 对齐参数 偏移地址 大小
char c1; // 1 0 1
short s; // 2 2 2
char c2; // 1 4 1
int i; // 2 6 4
}; // 在2字节对齐下,该结构体大小为10字节
#pragma pack() #pragma pack(4) // 以4字节对齐
struct Test2
{ // 对齐参数 偏移地址 大小
char c1; // 1 0 1
char c2; // 1 1 1
short s; // 2 2 2
int i; // 4 4 4
}; // 在4字节对齐下,该结构体大小为8字节
#pragma pack()
分析结果和前面程序的输出结果相同,结构体成员在内存中的位置如下图所示:

上面这个例子比较简单,我们再来看一下微软的一道笔试题
#include <stdio.h> #pragma pack(8) // 以8字节对齐
struct S1
{ // 对齐参数 偏移地址 大小
short a; // 2 0 2
long b; // 8 8 8
}; // 在8字节对齐下,该结构体大小为16字节 struct S2 // 结构体中包含了一个结构体成员,取其内部长度最大的数据成员作为其大小
{ // 对齐参数 偏移地址 大小
char c; // 1 0 1
struct S1 d; // 8 8 16
double e; // 8 24 8
}; // 在8字节对齐下,该结构体大小为32字节
#pragma pack() int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2)); return ;
}
使用gcc编译,程序执行结果如下,和我们分析的结果相同

【这里和唐老师课程中的结果不同,唐老师使用的编译器不支持8字节对齐,即 #pragma pack(8),我的这个gcc支持。】
我们再使用 VS2010编译器 和 BCC32编译器 测试一下上面的代码
VS2010编译器
D:\>cl test.c
用于 80x86 的 Microsoft (R) 位 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
BCC32编译器
D:\>bcc32 test.c
Borland C++ 5.5. for Win32 Copyright (c) , Borland
test.c:
Turbo Incremental Link 5.00 Copyright (c) , Borland D:\>test.exe // 这里和gcc结果不同是因为在该平台下sizeof(long) = 4
第24课 - #pragma 使用分析的更多相关文章
- 第24课 #pragma使用分析
#pragma是C语言留给编译器厂商进行扩展用的. 这个关键字在不同的编译器之间也许是不能够移植的. #pragma简介 #pragma message #pragma message打印的消息并不代 ...
- 跟我一起学编程—《Scratch编程》第24课:幸运大转盘
同学你好,欢迎来到<跟我一起学编程>,我是包老师.这是<Scratch3.0编程>课程的第24课,我这节课教你做一个抽奖游戏:幸运大转盘. 学习目标: 1. 能够熟练使用造型工 ...
- Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation
原文:Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...
- 第50课 C++对象模型分析(上)
1. 回归本质 (1)class是一种特殊的结构体 ①在内存中class依旧可以看作变量的集合 ②class与struct遵循相同的内存对齐规则 ③class中的成员函数与成员变量是分开存放的.即每个 ...
- #pragma使用分析
#pragma简介 #pragma用于指示编译器完成一些特定的动作 #pragma所定义的很多指示字是编译器特有的 #pragma在不同的编译器间是不可移植的 预处理器将忽略它不认识的#pragma指 ...
- 第51课 C++对象模型分析(下)
1. 单继承对象模型 (1)单一继承 [编程实验]继承对象模型初探 #include <iostream> using namespace std; class Demo { protec ...
- [转][Swust OJ 24]--Max Area(画图分析)
转载自:http://www.cnblogs.com/hate13/p/4160751.html Max Area 题目描述:(链接:http://acm.swust.edu.cn/problem/2 ...
- JAVA_SE基础——24.面向对象的内存分析
黑马程序员入学blog ... 接着上一章的代码: //车类 class Car{ //事物的公共属性使用成员变量描述. String name; //名字的属性 String color; //颜色 ...
- 24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下: 1.1我们以上图的read id(读ID)为例,它的时序图如下: 首先需要使能CE片选 1)使能CLE 2)发送0X90命 ...
随机推荐
- SQL Server 异常代码处理
SQL Server使用TRY...CATCH 结构实现TSQL语句的错误处理,TRY命令负责监控语句执行的情况,如果有TSQL语句发生异常,并且严重级别(Severity Level)大于10,并且 ...
- mysql无法远程连接问题(ERROR 1045 (28000): Access denied for user 'root')
mysql版本 : 8.0.21 使用mysql 作为nextcloud的数据库.之前使用挺正常的,因为被黑客勒索过一次,重新启动了一个mysql的docker镜像. 结果数据库配置老是失败,next ...
- Antd cracoTs Js 配置流程
JS:文档:0.1.4 配置 js 环境.note链接:http://note.youdao.com/noteshare?id=e32fa75c1baa014b5819fa5e22887dbc& ...
- JavaScript学习系列博客_20_JavaScript 作用域
作用域 - 作用域指一个变量的作用的范围 - 在JS中一共有两种作用域 1.全局作用域 - 直接编写在script标签中的JS代码,都在全局作用域- 全局作用域在页面打开时创建,在页面关闭时销毁 - ...
- ceph 快照,克隆
转载 https://my.oschina.net/wangzilong/blog/1595081 ceph 快照,克隆 ceph是一个非常好的后端存储系统.其中包括最常用的块存储,对象存储,文件系统 ...
- Camera学习--光源
进入CV 领域,视频图像的成像,最前端的camera,camera的sensor 以及影响成像质量的光源,噪声等因素是绕不开的问题. 那么今天就从成像的光源说起. 标准光源(Standard Ligh ...
- 「从零单排canal 07」 parser模块源码解析
基于1.1.5-alpha版本,具体源码笔记可以参考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_read ...
- Java算法——回溯法
回溯法一种选优搜索法,又称试探法.利用试探性的方法,在包含问题所有解的解空间树中,将可能的结果搜索一遍,从而获得满足条件的解.搜索过程采用深度遍历策略,并随时判定结点是否满足条件要求,满足要求就继续向 ...
- 设计模式 | Spring中用到的设计模式,你知道几个?
设计模式无处不在,因为它就来自于我们的日常生活,提炼于生活经验. 正握在你手中的手机,不能用220V的电压直接充电,需要一个专门的电源适配器(充电器)才行.摆在你桌上的电脑也是一样的,都需要" ...
- 一次完整的JVM堆外内存泄漏故障排查记录
前言 记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些JVM内存分配机制以及常用的JVM问题排查指令和工具分享,希望对大家有所帮助. 在整个排查过程中,我也走了不少弯路,但是在文章中我 ...