一、#pragma的学习定位

在所有的预处理关键字中,#pragma 的用法应该是最复杂的,然而然并卵,
虽然复杂但是在实际开发中用的并不多,很多人做了很久的C开发,但是可能都没有听过#pragma

不过,虽然在我们自己的C应用程序中,#pragma虽然用的不是特别多,但是在源码中还是会看见的,
因为这个原因,我们这里需要介绍一下#pragma,免得大家看到后不认识

所以像#pragma这种难度,但是又没有什么大用的知识,我们应该本着务实的态度来学习

怎么务实呢?

明白#pragma基本功能即可,如果以后因为某种特殊原因,你用到了#pragma的深入用法时,
再根据实际情况去有针对性的深入学习即可,这种有针对性的学习,其学习效率更高


二、#pragma的作用

通过#pragma指定的某些设置,然后通过这些设置告诉编译器在预编译或者编译时,完成某些特定的事情


三、#pragma的一个有意思的特点

#pragma 与其它“预编译关键字”的一个比较大的区别是,其它的预编译关键字都是在“预编译”阶段被处理的,
预编译之后就看不到了,但是#pragma就不一定了,#pragma的大多数用法是在预编译阶段处理的,

但是有些少数情况是在第二阶段“编译”时处理的

四、#pragma的使用格式

#pragma parameter(参数)

通过指定不同的参数(parameter),告诉编译器在编译时完成某些特定事情

#pragma的参数表:
* 表示仅C++支持,其它的C/C++都支持

alloc_text    comment     init_seg*    optimize         auto_inline       component
inline_depth  pack        bss_seg      data_seg         inline_recursion  pointers_to_members*
check_stack   function    intrinsic    setlocale        code_seg          hdrstop
message       vtordisp*   const_seg    include_alias    once              warning

很多同学看到这个参数表时估计都被吓到了,其实不用害怕,因为这里面的很多参数我们几乎用不到,
所以这里仅仅只是列举出来让你见见而已,我们只需要通过其中某几个参数来介绍下#pragma的基本使用即可


五、#pragma的常用参数

1. #pragma once

作用

#ifndef一样,可以用于防止头文件的重复包含,只不过ifndef方式是最古老、最普遍的方式,
所有的C/C++编译器都支持,而#pragma once是一个比较新的方式,有些编译器可能并不支持#pragma once方式

不过经过多年的发展,现在大多数编译器都支持#pragma once的这种写法了,
比如gcc编译器就支持这种写法,所以当你在某些头文件看到了#pragma once的写法时不要蒙圈

不过目前主流的还是#ifndef方式,不过为了让程序有更好的移植性,
我们建议大家还是多使用#ifndef的方式,毕竟编译器对这种方式的支持性更好些

有些头文件可能会比较奇葩,在文件头上,#pragma once#ifndef方式都有,编译器支持哪种就用哪种

#pragma once 使用举例:helloworld.h

#pragma once  //预编译后,#pragma once就没有了

struct stu
{
    int num;
    char name[20];
};

helloworld.c

#include <stdio.h>
#include "helloworld.h"
#include "helloworld.h" //重复include

int main(void)
{
    return 0;
}

查看预编译的结果:

# 3 "helloworld.h"
struct stu   //struct stu结构体类型的定义只有一个,说明#pragma once成功的防止了头文件内容的重复。
{ 
    int num;
    char name[20];
};

# 3 "helloworld.c" 2
int main(void)
{
    fun(10);
    return 0;
}

#pragma once#ifndef实现方式的区别
这二者都能实现防止头文件内容重复,只不过在实现原理上有一点区别

(a)#ifndef

如果helloworld.h使用#ifndef方式来实现的话,两次includehelloworld.h的内容都
会被复制到到.c文件中,然后使用#ifndef保留第一次包含内容,然后去掉重复内容

#ifndef的原理就是,先把所有.h的内容都复制到.c中(包括重复的),然后使用#ifndef来去掉
重复的内容。

(b)#pragma once

#pragma onceifndef有所不同,使用#pragma once方式来实现时,只会复制第一次incldue.h的内容到.c中,
后续重复inlcude.h的内容根本不会被复制到.c中,后续重复的inlcude会被直接被判定无效

从以上介绍可以看出,从效率上来说,#pragma once的效率比#ifndef方式更高,
因为使用#ifndef方式时,所有重复include.h的内容都需要被复制到.c中,但是#pragma once不会

2. #pragma auto_inline

这一个与“内联函数”有关,后面在函数章节讲内联函数时,再来介绍。

3. #pragma message(message string)

作用

在“编译”时打印提示信息,注意我说的是在“编译”时而不是在“预编译”时,
也就是说#pragma message这话是在第二阶段“编译”时才会起作用的

2)使用举例
(a)例子1
文件:hellowolrd.c

#include <stdio.h>

#ifndef _ARM

#pragma message( "macro _ARM not defined" )

#endif

int main(void)
{
    return 0;
}

查看预编译结果:

# 7 "helloworld.c"
#pragma message( "macro _ARM not defined" )
# 7 "helloworld.c"


# 11 "helloworld.c"
int main(void)
{
    return 0;
}

预编译后,#pragma message( "Pentium processor build" ) 还在,说明还没有被处理

编译:

gcc -S helloworld.i -o hellowolrd.s

输出信息:

helloworld.c:5:9: note: #pragma message: macro _ARM not defined
#pragma message( "macro _ARM not defined"

通过以上的这个例子,我们也可以通过#pragma message这种方式,在“编译阶段”提示某个宏有没有被定义,
不过我们前面也说过,有关宏的报错,应该尽量在预编译阶段使用#error来报错会更好,

(b)例子2
文件:hellowolrd.c

#include <stdio.h>

#pragma message( "Compiling " __FILE__ )  //提示目前正在编译什么文件

int main(void)
{
    return 0;
}

编译:gcc helloworld.c

helloworld.c:3:9: note: #pragma message: Compiling helloworld.c
#pragma message( "Compiling " __FILE__ )

通过#pragma message可以在编译时的打印提示信息,有编译过大型C工程的同学估计都知道,
在编译的过程中,往往打印很多的编译时的提示信息,告诉你目前编译到什么位置了,
目前被编译文件是哪一个,编译状态是怎样的,其实这些信息大多都是#pragma message打印出来的

通过提示的这些信息,可以向编译者提示编译状态,特别编译开源的官方C源码时,
打印提示信息还是很重要的,因为通过这些提示信息,可以帮助排查错误,实现源码的移植

你将来写大型C工程项目时,你也可以使用#pragma message来打印编译时提示信息

4. #pragma pack:内存对齐

后面讲到结构体的内存对齐时,我们再来介绍这个玩意。

1)bss_seg:修改和设置.bss
2)data_seg:修改和设置.data
3)const_seg:修改和设置.ro.data
4)code_seg:修改和设置.text

这些简单了解即可,因为如果要研究清楚的话,其实内容还比较深,但是就算你研究明白了,
对于我们实际开发的意义并不大,因此这里只简单提下

有关#pragma其它参数,我们这里也是持同样的观点,以后真的当你遇到某种特殊参数时,你再有针对性的去学习,效果会更好