Summary
1)#pragma
用于 指示编译器
完成一些特定的动作;#pragma
所定义的很多指示字是编译器特有的
,所以在不同的编译器间是不可移植
的
预处理器
将忽略
它不认识的#pragma指令- 不同的编译器可能以不同的方式解释同一条#pragma指令
- 一般用法:
#pragma parameter
,不同的parameter参数语法和意义各不相同。
#pragma message
在编译时输出信息到编译输出窗口;和#error、#warning不同,#pragma message仅仅表示一条提示信息,不代表错误。(vc、bcc和gcc三款编译器行为不同)【【C进阶】24、#pragma分析】3)
#pragma once
用于保证头文件只被编译一次
; #pragma once
是编译器相关
的,不一定被支持。(vc和gcc支持,bcc不支持)4)
#ifndef...#define...#endif
也可以用来防止头文件被重复包含,和#pragma once
有何不同?#ifndef
方式是C语言支持
的,用在各个编译器都可以;如果一个.h被include了n次
,预编译器就会判断n次
这个头文件是否已经包含了。#pragma once
方式是编译器相关
的,不一定所有编译器都支持;预编译器只会处理一次
,后面不再判断。
不同类型的数据在内存中按照一定的规则排列
,不一定是顺序的
一个接一个的排列
- CPU对内存的读取不是连续的,而是
分成块读取
的,块的大小只能是2的幂
,1、2、4、8...字节 - 当读取操作的数据未对齐,则需要两次
总线周期
来访问内存,因此性能
会大打折扣 - 某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则会产生硬件异常
编译器默认的对齐方式为4字节对齐
, #pragma pack(n)
可以调整编译器的默认对齐方式(vc和bcc编译器支持8字节对齐,但是gcc不支持)8)
内存对齐的规则
:文章图片
·#pragma分析
#pragma
用于 指示编译器
完成一些特定的动作;#pragma
所定义的很多指示字是编译器特有的,所以在不同的编译器间是不可移植
的预处理器
将忽略
它不认识的#pragma指令- 不同的编译器可能以不同的方式解释同一条#pragma指令
- 一般用法:
#pragma parameter
,不同的parameter参数语法和意义各不相同。
- message参数在大多数的编译器中都有相似的实现
- message参数在编译时输出消息到编译输出窗口中
- message用于条件编译中可提示代码的版本信息
#include #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! #endifint 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仅仅代表一条编译信息
,不代表程序错误。
#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 #include "test.h" #include "test.h"int main() { return 0; }
不同编译器的编译结果: bcc编译器:bcc32 test.c 编译输出:Variable 'aa' is initialized more than oncevc编译器:cl test.c 编译输出:成功gcc编译器:gcc test.c 编译输出:成功分析:#pragma once预处理指示字,vc和gcc都可以识别,但是bcc就无法识别,直接忽略了,然后aa就会被定义2次,造成编译错误
提高效率
,可以这两种方式同时使用
:#ifndef _TEST_H_
#define _TEST_H_#pragma once// code#endif
3、#pragma pack和内存对齐 3.1 什么是内存对齐?
不同类型的数据在内存中按照一定的规则排列
,不一定是顺序的
一个接一个的排列
文章图片
- CPU对内存的读取不是连续的,而是
分成块读取
的,块的大小只能是2的幂
,1、2、4、8...字节 - 当读取操作的数据未对齐,则需要两次
总线周期
来访问内存,因此性能
会大打折扣 - 某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则会产生硬件异常
编译器默认的对齐方式为4字节对齐
#pragama pack
可以调整编译器的默认对齐方式#include #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占用的内存大小发生了变化。
- 第一个成员起始于
0偏移处
- 每个成员按照
对齐参数
进行对齐(类型大小
和pack参数
中较小的
一个)
偏移地址
必须能被对齐参数
整除
(上一个成员的offset+size位置,偏移地址 / 对齐参数 == 0)结构体类型成员
的类型大小取其内部长度最大的数据
成员作为其大小
- struct的总长度必须为
所有对齐参数的整数倍
文章图片
#include #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
{packoffsetmemberSize
char c;
101
struct Test1 st;
448
double d;
4128
}总大小:20
本文总结自“狄泰软件学院”唐佐林老师《C语言进阶课程》。
如有错漏之处,恳请指正。
推荐阅读
- c/c++|有感 Visual Studio 2015 RTM 简介 - 八年后回归 Dot Net,终于迎来了 Mvc 时代,盼走了 Web 窗体时代...
- C/C++|C/C++ basis 02
- Qt实战|Qt+OpenCV联合开发(二十一)--图像翻转与旋转
- Qt实战|Qt+OpenCV联合开发(十四)--图像感兴趣区域(ROI)的提取
- Qt实战|Qt+OpenCV联合开发(十三)--通道分离与合并
- opencv|Qt+OpenCV联合开发(十六)--图像几何形状绘制
- Qt实战|Qt+OpenCV联合开发(十七)--随机数与随机颜色
- SNAT的MASQUERADE地址选择与端口选择
- IPTABLES的连接跟踪与NAT分析
- IPVS分析