注意:#开头的指示字,都是用于预处理器的
#error
#error 用于生成一个编译错误消息
用法
1 |
message 不需要用双引号包围
#error 编译指示字用于自定义程序员特有的编译错误消息
类似的,#warning 用于生成编译警告
#error 是一种预编译器的指示字
#error 可用于提示编译条件是否满足
1 |
编译过程中的任意错误信息意味着无法生成最终的可执行程序;
示例
1 |
|
在 C 文件中编译 C++ 的代码肯定是会报错的此时如果使用 C++ 编译器进行编译是没有问题的
g++ test.c // ok
但是如果我们此时使用 C 编译器进行编译则会出现问题:
test/demo.c:4:10: error: #error This file should be processed with C++ compiler.
4 | #error This file should be processed with C++ compiler.
| ^~~~~
test/demo.c:7:1: error: unknown type name ‘class’
7 | class CppClass {
| ^~~~~
test/demo.c:7:16: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
7 | class CppClass {
|上述可以看出他输出了我们自定义的宏错误,程序会判断 __cplusplus 这个 C++ 关键字是否存在如果不存在则会执行里面的代码并中止程序的执行;
#line
#line 用于强制指定新的行号和编译文件名,并对源程序的代码重新编号
用法
1 |
|
示例
1 | // demo.c |
上述代码运行后会产生错误信息:
kay.c: In function ‘main’:
kay.c:3:28: error: expected ‘;’ before ‘return’此时我们看到,他的提示信息改变了,文件名变成了我们输出的文件名,提示的行号也从第 6 行变成了第 3 行;
他会从 #line 1 “kay.c” 下开始计算为第一行
#pragma
#pragma指示使每个编译程序在保留C和C++语言的整体兼容性时提供不同机器和操作系统特定的功能。一般是由编译器厂家在使用的;
- #pragma 用于指示编译器完成一些特定的动作
- #pragma 所定义的很多指示字是编译器特有的
- #pragma 在不同的编译器间是不可移植的
- 预处理器将忽略它不认识的 #pragma 指令
- 不同的编译器可能以不同的方式解释同一条 #pragma 指令
#pragma
是 C 和 C++ 中的一种预处理指令,用于向编译器提供特定的指示。这些指示通常与编译器的实现相关,可以控制编译器的行为、优化选项或警告设置。他是编译器所制定的,而类似 #messge 是类似 C 语言的语法;
用法
1 | // 一般用法 |
注:不同的 parameter 参数语法和意义各不相同
示例
- message 参数在大多数的编译器中都有相似的实现
- message 参数在编译时输出消息到编译输出窗口中
- message 用于条件编译中可提示代码的版本信息
1 |
与 #error 和 #waring 不同,#pragma meaasge 仅仅代表一条编译消息,不代表程序错误。
1 |
|
编译: gcc -DANDROID40 demo.c,他会输出提示信息:
demo.c:10:17: note: ‘#pragma message: Compiler Android SDK 4.0…’
10 | #pragma message(“Compiler Android SDK 4.0…”)
|运行: ./a.out
Android 4.0
#pragma 后面跟着的参数也许在每个编译器都有实现,但是可能实现的方式是不同的
#pragma once
- #pragma once 用于保证头文件只被编译一次
- #pragma once 是编译器相关的,不一定被支持
#ifndef 宏保护:编译器需要检查每个包含的宏定义,因此在某些情况下可能效率稍低。他要去判断检查这个宏是否被定义,没有就展开,被定义了就不执行中间的内容
#pragma once:编译器可以更高效地处理它,因为它只需跟踪文件的路径,而不需要处理宏定义的条件。他是记住头文件的路径或者文件名,下次遇到就自动跳过,减少了判断时的时间消耗。
二者可以结合使用
#pragma pack
什么是内存对齐
- 不同的数据类型在内存中按照一定的规则排列
- 而不一定是顺序的一个接着一个排列
1 |
|
为什么需要内存对齐?
- CPU 对内存的读取不是连续的,而是分块读取的,块的大小只能是1,2,4,8,16,…个字节
- 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因为性能会大打折扣
- 某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则产生硬件异常
#pragma pack 用于指定内存对齐的方式
1 |
|
#pragma pack 指定内存对齐的方式为:
struct 占用的内存大小
- 第一个成员起始于0 偏移处
- 每个成员按其大小和 pack 参数中较小的一个进行对齐
- 偏移地址必须能被对齐参数(其大小和 pack 中较小的一个)整除
- 结构体成员的大小取其内部长度最大的数据成员作为其对齐大小
- 结构体总长度必须为所有对其参数的整数倍
编译器在 32 位系统下默认情况下按照 4 字节大小对齐(也就是默认情况下是#pragma pack(4))
编译器在 64 位系统下默认情况下按照 8 字节大小对齐(也就是默认情况下是#pragma pack(8))
上面的 12B 和 8B 分别是如何计算的?
Tset1:
c1 占一个字节 pack 是四个字节,按照 1 个字节去对齐,存入 0 下标位置
s 占两个字节,pack 是四个字节,按照 2 个字节去对其,存入下标为 2 的整数倍的最小地址处,也就是下标 2 处
c2 占 1 个字节,pack是四个字节,按照 1 个字节去对其,存入下标为 1 的整数倍的最小地址处的地址,也就是下标为 4 处
i 占4 个字节,pack也是四个字节,按照 4 个字节对齐,存入下标为 4 的整数倍的最小地址处地址处,也就是下标为 8处,
综上占了 12 个字节正好是 1 2 4 的整数倍,所以一共占了 12 个字节。
1 |
|
总结
- #error 用于自定义一条编译错误信息
- #warning 用于自定义一条编译警告信息
- #rror 和 #warning 常用于条件编译的情形
- #line 用于强制指定新的行号和编译文件名
- #pragma 用于指示编译器完成一些特定动作
- #pragma 所定义的很多指示字是编译器特有的
- #pragma message 用于自定义编译信息
- #pragma once 用于保证头文件只被编译一次
- #pragma pack 用于指定内存对齐方式