文件夹路径TMP C:中含有无效判断字符串是否为路径

C语言中获取和改变目录的相关函数总结
转载 & & 投稿:goldensun
这篇文章主要介绍了C语言中获取和改变目录的相关函数总结,包括getcwd()函数和chdir()函数以及chroot()函数的使用方法,需要的朋友可以参考下
C语言getcwd()函数:取得当前的工作目录
#include &unistd.h&
定义函数:
char * getcwd(char * buf, size_t size);
函数说明:getcwd()会将当前的工作目录绝对路径复制到参数buf 所指的内存空间,参数size 为buf 的空间大小。
1、在调用此函数时,buf 所指的内存空间要足够大。若工作目录绝对路径的字符串长度超过参数size 大小,则返回NULL,errno 的值则为ERANGE。
2、倘若参数buf 为NULL,getcwd()会依参数size 的大小自动配置内存(使用malloc()),如果参数size 也为0,则getcwd()会依工作目录绝对路径的字符串程度来决定所配置的内存大小,进程可以在使用完次字符串后利用free()来释放此空间。
返回值:执行成功则将结果复制到参数buf 所指的内存空间, 或是返回自动配置的字符串指针. 失败返回NULL,错误代码存于errno.
#include &unistd.h&
char buf[80];
getcwd(buf, sizeof(buf));
printf("current working directory : %s\n", buf);
current working directory :/tmp
C语言chdir()函数:改变当前的工作目录
#include &unistd.h&
定义函数:
int chdir(const char * path);
函数说明:chdir()用来将当前的工作目录改变成以参数path 所指的目录.
返回值执:行成功则返回0, 失败返回-1, errno 为错误代码.
#include &unistd.h&
chdir("/tmp");
printf("current working directory: %s\n", getcwd(NULL, NULL));
current working directory :/tmp
C语言chroot()函数:改变文件根目录
#include &unistd.h&
定义函数:
int chroot(const char * path);
函数说明:chroot()用来改变根目录为参数path 所指定的目录。只有超级用户才允许改变根目录,子进程将继承新的根目录。
返回值:调用成功则返回0, 失败则返-1, 错误代码存于errno.
错误代码:
1、EPERM 权限不足, 无法改变根目录。
2、EFAULT 参数path 指针超出可存取内存空间。
3、ENAMETOOLONG 参数path 太长。
4、ENOTDIR 路径中的目录存在但却非真正的目录。
5、EACCESS 存取目录时被拒绝。
6、ENOMEM 核心内存不足。
7、ELOOP 参数path 有过多符号连接问题。
8、EIO I/O 存取错误。
/* 将根目录改为/tmp, 并将工作目录切换至/tmp */
#include &unistd.h&
chroot("/tmp");
chdir("/");
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具如c:\windows\*\*.*与c:\windows\**\*.*的区别?(*与**区别)
%temp%与%tmp%是否是指同一目录?
* 表示任意多个字符
**或*? 表示零个或多个含有反斜杠的字符,即包含子文件夹
详解一下。
第一个:c:\windows\*\*.*
如果把windows目录当作一级文件夹的话,这个路径表示windows目录下所有二级文件夹里的文件,但是不包括三级或以上的子文件夹里面的文件。
第二个:c:\windows\**\*.*
这个路径表示windows下面,所有子文件夹里的所有文件。
一个*和两个*的区别就是,一个星只能表示任意的一个文件夹。两个*则可以表示目录下的所有子目录,二级的,三级的,四级的,N级的,都可以。希望这么说你能明白,o(≥ω≤)o
%TEMP% 和 %TMP% 是一样的,都表示 C:\Documents and Settings\当前用户名\Local Settings\Temp 这个路径。
一般文件的命名不会影响到文件本身的使用。
如果带有类似 ~ ! # $ ^ * ? 符号的文件不能使用,一般有三个原因;一:文件本身已经损坏,比如拷贝文件不完整...
Temp这个文件下面的文件都是临事文件,也是病毒经常光顾的地方.
解决方法:
1,请到以下地址下载第一杀木马软件“最新版ewido” 免费下载使用,永久升级。自...
答: 别按F1,按DEL,然后进入BIOS,选Load BIOS Default或LOAD BIOS SETUP,然后保存退出就行了.
答: 最简单的:
先在别人的计算机上安装你弄的含病毒文件,然后使用计算机自带的远程控制程序操控.
!!!(以上方法我也没试过,朋友介绍的!)
答: 我也是公司网管 我来告诉你 2种方法 但是都必须买一个高级的企业路由器
1 把网站地址屏蔽掉 或者封掉下载端口 这样根本就上不了了
2 把每个人的下载流量控制掉...
大家还关注
Copyright &
Corporation, All Rights Reserved
确定举报此问题
举报原因(必选):
广告或垃圾信息
激进时政或意识形态话题
不雅词句或人身攻击
侵犯他人隐私
其它违法和不良信息
报告,这不是个问题
报告原因(必选):
这不是个问题
这个问题分类似乎错了
这个不是我熟悉的地区
相关问答:123456789101112131415tmpfiles.d
中文手册 [金步国]tmpfiles.d 中文手册译者:版权声明本文译者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布。无担保:本文译者不保证译文内容准确无误,亦不承担任何由于使用此文档所导致的损失。自由使用:任何人都可以自由的阅读/链接/打印此文档,无需任何附加条件。名誉权:任何人都可以自由的转载/引用/再创作此文档,但必须保留译者署名并注明出处。其他作品本文译者十分愿意与他人分享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有的作品集: [
]联系方式由于译者水平有限,因此不能保证译文内容准确无误。如果你发现了译文中的错误(哪怕是错别字也好),请来信指出,任何提高译文质量的建议我都将虚心接纳。Email(QQ):在QQ邮箱 .
systemd-235名称tmpfiles.d — 配置如何创建、删除、清理易变文件与临时文件
大纲/etc/tmpfiles.d/*.conf/run/tmpfiles.d/*.conf/usr/lib/tmpfiles.d/*.conf描述systemd-tmpfiles
使用上述三个目录中的配置文件
来决定如何创建、删除、清理
易变文件与临时文件以及易变目录与临时目录。
这些文件与目录通常位于
/run, /var/run(指向 /run 的软连接),
/sys, /proc,
/var 目录中。系统的守护进程经常需要在
/run 目录下拥有专属的运行时目录,以存放套接字或管道之类的文件。
对于这个目的,应该使用单元文件中的
RuntimeDirectory= 选项(参见
手册)。配置目录及其优先级配置文件的名称必须符合
package.conf 或
package-part.conf 格式。
当需要明确的将某部分(part)配置提取出来,以方便用户专门针对这部分进行修改的时候,
应该使用第二种命名格式。
对于不同目录下的同名配置文件,
仅以优先级最高的目录中的那一个为准。
具体说来就是:
/etc/ 的优先级最高、
/run/ 的优先级居中、
/usr/lib/ 的优先级最低。
软件包应该将自带的配置文件安装在 /usr/lib/ 目录中,
而 /etc/ 目录仅供系统管理员使用。
所有的配置文件,无论其位于哪个目录中,都统一按照文件名的字典顺序处理。
如果在多个配置文件中设置了同一个路径(文件或目录),
那么仅以文件名最靠前(字典顺序)的那一个为准,
其他针对同一个路径的配置项将会作为警告信息记录到错误日志中。
如果有两行互为前后缀,
那么始终是先处理前缀、再处理后缀。
所有带有shell风格通配符的行,
都在所有不带通配符的行之后处理。
如果有多个操作符应用于同一个文件(例如 ACL, xattr, 文件属性调整),
那么将始终按固定的顺序操作。
对于其他情况,
文件与目录总是按照它们在配置文件中出现的顺序处理。
如果系统管理员想要屏蔽 /usr/lib/ 目录中的某个配置文件,
那么最佳做法是在 /etc/ 目录中
创建一个指向 /dev/null 的同名符号链接,
即可彻底屏蔽 /usr/lib/ 目录中的同名文件。
配置文件格式配置文件的格式是每行对应一个路径,包含如下字段:
类型, 路径, 权限, 属主, 属组, 寿命, 参数#Type Path
Age Argument
0755 root root 10d -
/tmp/foobar -
/dev/null字段值可以用引号界定,并可以包含C风格的转义字符。类型(Type)"类型"字段由一个单独的、表示类型的字母与一个可选的感叹号(!)组成。
可以识别的类型如下:f若指定的文件不存在,则创建它,否则什么也不做。
若设置了"参数"字段,则将其内容写入指定的文件。
不追踪软连接。F若指定的文件不存在,则创建它,否则清空已有文件。
若设置了"参数"字段,则将其内容写入指定的文件。不追踪软连接。w若指定的文件存在,则将"参数"字段的内容写入该文件,否则什么也不做。
注意:不会在"参数"字段内容的末尾添加额外的换行符,
可以在"参数"字段中使用C语言风格的转义字符。
可以在"路径"字段中使用shell风格的通配符。
追踪软连接。
d创建指定的目录并赋于指定的UID/GID与权限。
如果指定的目录已经存在,那么仅调整UID/GID与权限。
如果指定了"寿命"字段,那么该目录中的内容将遵守基于时间的清理策略。D与 d 类似,
但是如果使用了 --remove 选项,那么将会清空目录中的所有内容。
e与 d 类似,但是并不创建原本不存在的目录。
可以在"路径"字段中使用shell风格的通配符。
要想让此类型有效,必须至少明确设置 权限, 属主, 属组, 寿命 字段之一。
如果"寿命"字段的值为 "0" ,
那么每次运行 systemd-tmpfiles --clean 命令都会无条件的清空该目录。
这个特性经常与 ! 一起使用(参见后文的"例子"小节)。
如果指定的路径不存在,
并且该路径位于一个Btrfs子卷中,
那么按照指定的路径创建子卷。
否则,创建一个普通的目录(与 d 一样)。
仅对Btrfs文件系统有意义。
此处创建的子卷不会被分配给任何 higher-level 配额组(quota group),
如果想要创建简单的配额组层次关系,
那么应该使用下面的 q 或 Q 。
q与 v 类似,
但会将所创建的新子卷分配给父子卷(父目录)
所属的 higher-level 配额组(quota group)。
这样可以确保作用于父子卷(父目录)的 higher-level 配额组
可以包含此处创建的新子卷(子目录)。
仅对Btrfs文件系统有意义,
在非Btrfs文件系统上与 d 等价。
如果指定的子卷(路径)已经存在,
并且已经被分配给了一个或多个 higher-level 配额组,那么不修改任何现有的配额层次关系。
参见下面的 Q 以及
手册,以了解更多关于btrfs的配额组(quota group)的概念。
Q与 q 类似,
但并不直接复用父子卷(父目录)的 higher-level 配额组(quota group),
而是首先找到父子卷(父目录)的
最低级非叶子配额组(也就是倒数第二级配额组),
然后在此配额组与最末端的叶子配额组之间,
插入一个level值减一的"中间配额组",
并且与新建的子卷共享同一个ID。
如果父子卷(父目录)不存在 higher-level 配额组,
那么插入一个level值为255且ID与新建子卷相同的"中间配额组",
并且这个新建的"中间配额组"将被指定为
父子卷(父目录)的 higher-level 配额组(quota group),
同时,新建子卷的叶子配额组也会分配给它(中间配额组)。
单就新建子卷自身而言,
q 与 Q 在实际效果上并无不同,
但是通过插入一个新的 higher-level 配额组,
Q 可以实现对该新建的子卷以及
未来创建在该子卷之下的下级子卷进行统一的配额限制。
这样,在通过 q 与
Q 创建子卷的同时,也一起实现了一颗"配额子树"。
创建的子卷都将拥有一个自己专属的配额层次结构(配额子树),
可用于容纳将来创建的下级子卷。
创建的子卷都没有自己专属的配额子树,
其自身会被添加到直属父子卷(父目录)所属的配额子树中。
Q 通常用于
/home 或 /var/lib/machines
这样的目录,
其特点是:
不但需要包含多个下级子卷,
而且需要将所有下级子卷视为一个整体进行配额限制。
q 通常用于
/var 或 /var/tmp
这样的目录,
其特点是:
要么不需要包含下级子卷,
要么不需要将所有下级子卷视为一个整体进行配额限制。
与 q 一样,Q 也不会改变任何已经存在的配额层次关系,也就是说,
如果指定的子卷(路径)已经存在,并且已经被分配给了一个或多个 higher-level 配额组,
那么不修改任何现有的配额层次关系。p, p+
若指定的管道(FIFO)不存在,则创建它,否则什么也不做。
后缀 + 表示:若指定的路径已存在一个非管道文件,
则先删除此文件再创建指定的管道文件。L, L+若指定的软连接不存在,则创建它,否则什么也不做。
后缀 + 表示:
若指定的路径已存在一个非软连接文件或目录,
则先删除此文件或目录再创建指定的软连接。
若"参数"字段为空,
那么将创建一个指向
/usr/share/factory/ 目录中同名文件的软连接。
注意,软连接的UID/GID与权限将被忽略。
若指定的字符设备不存在,则创建它,否则什么也不做。
后缀 + 表示:
若指定的路径已存在一个非字符设备的文件,
则先删除此文件再创建指定的字符设备。
因为udev并不管理运行时创建的静态设备节点,
所以建议在末尾加上"!",以确保仅在系统启动期间才创建此静态设备节点。
若指定的块设备不存在,则创建它,否则什么也不做。
后缀 + 表示:
若指定的路径已存在一个非块设备的文件,
则先删除此文件再创建指定的块设备。
因为udev并不管理在运行时创建的静态设备节点,
所以建议在末尾加上"!",以确保仅在系统启动期间才创建此静态设备节点。
若指定的文件或目录不存在,
则递归的从"参数"字段所指定的文件或目录复制,
否则什么也不做。
若"参数"字段为空,
那么将从 /usr/share/factory/ 目录中
递归的复制同名文件或目录。
不追踪软连接。x
在根据"寿命"字段清理过期文件时,
忽略指定的路径及该路径下的所有内容。
可以在"路径"字段中使用shell风格的通配符。
注意,这个保护措施对 r 与 R
在根据"寿命"字段清理过期文件时,
仅忽略指定的路径自身而不包括该路径下的其他内容。
可以在"路径"字段中使用shell风格的通配符。
注意,这个保护措施对
若指定的文件或目录存在,则删除它。
不可用于非空目录。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。R
若指定的文件或目录存在,则递归的删除它。
可用于非空目录。可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。z若指定的文件或目录存在,
则仅设置其自身的访问权限、属主、属组、重置SELinux安全上下文。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。Z
若指定的文件或目录存在,
则递归的设置其访问权限、属主、属组、重置SELinux安全上下文。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。
t若指定的文件或目录存在,
则仅设置其自身的SMACK标签。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。T若指定的文件或目录存在,
则递归的设置其SMACK标签。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。h若指定的文件或目录存在,则仅调整其自身的属性。
可以在"路径"字段中使用shell风格的通配符。"参数"字段的格式是 [+-=][aAcCdDeijsStTu]
具体解释如下:
+ 前缀表示添加属性(这是默认值);
- 前缀表示去除属性;
= 前缀表示设置属性(但对 "aAcCdDeijsStTu" 范围之外的其他属性没有影响);
而后缀字母
"aAcCdDeijsStTu" 则用于表示各种属性,
手册里的解释一致。
注意,将"参数"字段设为一个单独的
=(无后继字母)
表示重置所有 "aAcCdDeijsStTu" 代表的属性。
不追踪软连接。
H若指定的文件或目录存在,则递归的调整其属性。
可以在"路径"字段中使用shell风格的通配符。
不追踪软连接。"参数"字段的语法与 h 完全相同。
a, a+若指定的文件或目录存在,
则仅设置其自身的访问控制列表(POSIX ACL)。
可以在"路径"字段中使用shell风格的通配符。
后缀 + 表示将指定的项添加到已有的访问控制列表中。
除非基本权限已经存在或被明确指定,
否则 systemd-tmpfiles
将会自动根据"权限","属主","属组"字段添加所需的基本权限。
如果没有明确指定访问控制列表或者访问控制列表已经存在,
那么将会叠加上掩码。
不追踪软连接。
A, A+若指定的文件或目录存在,则递归的设置访问控制列表(POSIX ACL)。
其他与 a/a+ 完全相同。
不追踪软连接。使用了感叹号(!)标记的行,仅可用于系统启动过程中,
禁止用于运行中的系统(会破坏系统的正常运行)。
未使用感叹号(!)标记的行,
可以在任意时间安全的执行(例如升级软件包的时候)。
systemd-tmpfiles 仅在明确使用了
--boot 选项的时候才会执行使用了感叹号(!)标记的行。
# 确保默认创建此目录
d /tmp/.X11-unix 1777 root root 10d
# 仅在系统启动时清理X11的锁文件(但在运行时禁止删除这些文件)
r! /tmp/.X[0-9]*-lock
本例中的第二行仅在明确使用了 --boot 选项的时候才会执行,
因为它会破坏正在运行中的系统。
但是第一行则无此限制,可以在任何时候执行都不会对系统造成损害。路径(Path)"路径"字段是指文件系统路径,
并且支持几个简单的替换标记:
表 1. 可用于替换的标记标记含义解释"%m"Machine ID系统的"Machine ID"字符串。参见
手册。"%b"Boot ID系统的"Boot ID"字符串。参见
手册。"%H"Host name系统的主机名(hostname)"%v"Kernel release内核版本(uname -r 的输出)"%%"Escaped %百分号自身(%)。使用"%%"表示一个真正的"%"字符。权限(Mode)
"权限"字段表示设置文件或目录的权限,
如果省略或设为 "-" 则表示使用默认权限,
也就是:对于目录使用"0755"、对于文件使用"0644",
但对于 z, Z 表示不修改现有的权限。
此字段对于
a 没有意义。可选前缀 "~" 的含义是掩码,
也就是将此字段当作权限掩码来使用。
换句话说,就是将此字段设置的值与现有的权限做"位与"运算。
举例来说,假设原有文件的权限是"0765",此字段的值是"~1550",
那么文件的最终权限将是"0540"。
如果省略了 sticky/SUID/SGID 位,
那么对于文件来说相当于全部清除,
而对于目录来说则相当于保持不变。
例如"~550",对于文件来说相当于"~0550",
而对于目录来说则相当于"~7550"。
此特性在实践中仅对 Z 有实际价值。属主,属组(UID,GID)"属主"与"属组"字段分别表示文件或目录的所属用户与所属用户组。
可以设为数字形式的UID/GIU值,也可以设为字符串形式的用户名称/组名称。
如果省略或者设为 "-" 则表示使用默认值"0"(root)。
但对于 z 与
Z 来说,省略或者设为
"-" 则表示不修改现有的"属主"与"属组"。
此字段对于 x,
a 没有意义。寿命(Age)
"寿命"字段用于判定在清理过期文件或子目录时应该删除哪些,
也就是决定了文件或子目录的"寿命"。
如果文件或子目录的最后改动时间(ctime)
与当前系统时间之差已经大于"寿命"字段的值,
那么这些文件或子目录将被删除。
此字段的值是一个时间长度,
可以使用下面的时间单位后缀:
可以同时使用多个时间单位,
例如"5m10s"表示5分10秒(也就是310秒)。
如果省略了时间单位,那么表示使用默认单位"秒"。
如果将此字段设为数字"0",
那么表示在每次清理时都无条件的删除所有文件或子目录。此字段仅对
X 有意义。如果省略此字段或将其设为
"-" ,那么表示不进行任何清理。如果此字段的值以
"~" 开头,
那么表示在每次清理时都无条件的保留指定目录直属的文件与子目录,
也就是仅清理直属子目录下的内容。参数(Argument)
"参数"字段对不同的"类型"有不同的作用:
对于 L 来说,用于指定软连接的目标路径。
对于 c, b 来说,
用于以"主设备号:次设备号"的格式设定设备节点的主/次设备号(十进制整数),
例如 "1:3" 。
对于 f, F, w 来说,
用于设置一个写入文件的短字符串(实际写入时会在末尾附加一个换行符)。
对于 C 来说,用于指定源文件或者源目录。
对于 t, T 来说,
用于指定将要设置的扩展属性。
对于 a, A 来说,用于指定将要设置的ACL属性。
对于 h, H 来说,用于指定将要设置的文件属性。
对于其他类型无意义,将被忽略。
例子例 1. 按照指定的UID/GID与权限创建目录
需要在系统启动时按照指定的属主/属组/权限创建两个目录# /usr/lib/tmpfiles.d/screen.conf
d /run/screens
1777 root screen 10d
d /run/uscreens 0755 root screen 10d12h
/run/screens 中超过10天的内容将被清理。
/run/uscreens 中超过10.5天的内容将被清理。例 2. 创建一个带有 SMACK 属性的目录D /run/cups - - - -
t /run/cups - - - - security.SMACK64=printing user.attr-with-spaces="foo bar"
该目录的属主将是"root"并且会被赋予默认权限。
目录中的内容将不会被定期清理,但可使用
systemd-tmpfiles --remove 命令清除。例 3. 创建一个不会被定期清理的目录
需要在系统启动时按照指定的属主/属组/权限创建一个目录,
并且禁止清理 /var/tmp 目录
# /usr/lib/tmpfiles.d/tmp.conf
d /var/tmp 1777 root root 30d
# /usr/lib/tmpfiles.d/abrt.conf
d /var/tmp/abrt 0755 abrt abrt -
例 4. 启用系统启动过程中的清理,以及基于时间的清理# /usr/lib/tmpfiles.d/dnf.conf
r! /var/cache/dnf/*/*/download_lock.pid
r! /var/cache/dnf/*/*/metadata_lock.pid
r! /var/lib/dnf/rpmdb_lock.pid
/var/cache/dnf/ - - - 30d
系统启动过程中将会清理 lock 文件。
/var/cache/dnf/ 目录中闲置超过30天的内容将会被删除。
例 5. 在启动时清空缓存目录# /usr/lib/tmpfiles.d/krb5rcache.conf
e! /var/cache/krb5rcache - - - 0
在启动时清空 /var/cache/krb5rcache/
下的所有内容。即使此目录不存在也不会创建它。C预编译, 预处理, C/C++头文件, 编译控制,
时间: 14:28:54
&&&& 阅读:1120
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
在所有的预处理指令中,#Pragma
指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为:
为参数,下面来看一些常用的参数。
(1)message
参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
message(&消息文本&)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。
假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
message(&_X86
activated!&)
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示&_ X86
activated!&。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 。
(2)另一个使用得比较多的pragma参数是code_seg。格式如:
["section-name"[,"section-class"]
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
(3)#pragma
(比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
(4)#pragma
hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma
startup指定编译优先级,如果使用了#pragma
package(smart_init)
,BCB就会根据优先级的大小先后编译。
(5)#pragma
"*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。
(6)#pragma
warning(disable:4507
不显示4507和34号警告信息
warning(once:4385)
4385号警告信息仅报告一次
warning(error:164)
把164号警告信息作为一个错误。
同时这个pragma
也支持如下格式:
这里n代表一个警告等级(1---4)。
)保存所有警告信息的现有的警告状态。
n)保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为n。
)向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
在这段代码的最后,重新保存所有的警告信息(包括和4707)。
(7)pragma
comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。
一、预编译头文件说明
  所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。
  预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是&pch&,所以编译结果文件是projectname.pch。
  编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx. h"指令,使用projectname.pch编译这条指令之后的所有代码。
  因此,所有的CPP实现文件第一条语句都是:#include "stdafx.h"。
  另外,每一个实现文件CPP都包含了如下语句:
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
  这是表示,如果生成调试版本,要指示当前文件的名称。__FILE__是一个宏,在编译器编译过程中给它赋值为当前正在编译的文件名称。
  VC.NET默认情况下使用预编译头(/Yu),不明白的在加入新.h文件后编译时总出现fatal error C1010: 在查找预编译头指令时遇到意外的文件结尾的错误。解决方法是在include头文件的地方加上#include "stdafx.h",或者打项目属性,找到&C/C++&文件夹,单击&预编译头&属性页。修改&创建/使用预编译头&属性为&不使用预编译头&。
  二、C/C++头文件一览
  C、传统 C++
#include &assert.h&    //设定插入点
#include &ctype.h&     //字符处理
#include &errno.h&     //定义错误码
#include &float.h&     //浮点数处理
#include &fstream.h&    //文件输入/输出
#include &iomanip.h&    //参数化输入/输出
#include &iostream.h&   //数据流输入/输出
#include &limits.h&    //定义各种数据类型最值常量
#include &locale.h&    //定义本地化函数
#include &math.h&     //定义数学函数
#include &stdio.h&     //定义输入/输出函数
#include &stdlib.h&    //定义杂项函数及内存分配函数
#include &string.h&    //字符串处理
#include &strstrea.h&   //基于数组的输入/输出
#include &time.h&     //定义关于时间的函数
#include &wchar.h&     //宽字符处理及输入/输出
#include &wctype.h&    //宽字符分类
  标准 C++ (同上的不再注释)
#include &algorithm&    //STL 通用算法
#include &bitset&     //STL 位集容器
#include &cctype&
#include &cerrno&
#include &clocale&
#include &cmath&
#include &complex&     //复数类
#include &cstdio&
#include &cstdlib&
#include &cstring&
#include &ctime&
#include &deque&      //STL 双端队列容器
#include &exception&    //异常处理类
#include &fstream&
#include &functional&   //STL 定义运算函数(代替运算符)
#include &limits&
#include &list&      //STL 线性列表容器
#include &map&       //STL 映射容器
#include &iomanip&
#include &ios&       //基本输入/输出支持
#include &iosfwd&     //输入/输出系统使用的前置声明
#include &iostream&
#include &istream&     //基本输入流
#include &ostream&     //基本输出流
#include &queue&      //STL 队列容器
#include &set&       //STL 集合容器
#include &sstream&     //基于字符串的流
#include &stack&      //STL 堆栈容器    
#include &stdexcept&    //标准异常类
#include &streambuf&    //底层输入/输出支持
#include &string&     //字符串类
#include &utility&     //STL 通用模板类
#include &vector&     //STL 动态数组容器
#include &cwchar&
#include &cwctype&
using namespace
  C99 增加
#include &complex.h&   //复数处理
#include &fenv.h&    //浮点环境
#include &inttypes.h&  //整数格式转换
#include &stdbool.h&   //布尔环境
#include &stdint.h&   //整型环境
#include &tgmath.h&   //通用类型数学宏
  三、预处理的由来
  在C++的历史发展中,有很多的语言特征(特别是语言的晦涩之处)来自于C语言,预处理就是其中的一个。C++从C语言那里把C语言预处理器继承过来(C语言预处理器,被Bjarne博士简称为Cpp,不知道是不是C Program Preprocessor的简称)。
  四、常见的预处理功能
  预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。
  文件包含:#include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。
  条件编译:#if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。
  布局控制:#pragma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。
  宏替换:#define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。
五、预处理指令
  预处理指令的格式如下:
#directive tokens
#符号应该是这一行的第一个非空字符,一般我们把它放在起始位置。如果指令一行放不下,可以通过&\&进行控制,例如:
#define Error if(error) exit(1)
#define Error if(error) exit(1)
  下面我们看一下常见的预处理指令:
#define 宏定义
#undef 未定义宏
#include 文本包含
#ifdef 如果宏被定义就进行编译
#ifndef 如果宏未被定义就进行编译
#endif 结束编译块的控制
#if 表达式非零就对代码进行编译
#else 作为其他预处理的剩余选项进行编译
#elif 这是一种#else和#if的组合选项 //后面有例子的
#line 改变当前的行数和文件名称
#error 输出一个错误信息
#pragma 为编译程序提供非常规的控制流信息
  下面我们对这些预处理进行一一的说明,考虑到宏的重要性和繁琐性,我们把它放到最后讲。
  六、文件包含指令
  这种预处理使用方式是最为常见的,平时我们编写程序都会用到,最常见的用法是:
#include &iostream& //标准库头文件
#include &iostream.h& //旧式的标准库头文件
#include "IO.h" //用户自定义的头文件
#include "../file.h" //UNIX下的父目录下的头文件
#include "/usr/local/file.h" //UNIX下的完整路径
#include "..\file.h" //Dos下的父目录下的头文件
#include "\usr\local\file.h" //Dos下的完整路径
  这里面有2个地方要注意:
  1、我们用&iostream&还是&iostream.h&
  我们主张使用&iostream&,而不是&iostream.h&:首先,.h格式的头文件早在98年9月份就被标准委员会抛弃了,我们应该紧跟标准,以适合时代的发展。其次,iostream.h只支持窄字符集,iostream则支持窄/宽字符集。还有,标准对iostream作了很多的改动,接口和实现都有了变化。最后,iostream组件全部放入namespace std中,防止了名字污染。
  2、&io.h&和"io.h"的区别
  其实他们唯一的区别就是搜索路径不同:对于#include &io.h& ,编译器从标准库路径开始搜索对于#include "io.h" ,编译器从用户的工作路径开始搜索。
  七、编译控制指令
  这些指令的主要目的是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。使用格式,如下:
  1、如果identifier为一个定义了的符号,your code就会被编译,否则剔除。
#ifdef identifier
  2、如果identifier为一个未定义的符号,your code就会被编译,否则剔除。
#ifndef identifier
  3、如果expression非零,your code就会被编译,否则剔除。
#if expression
  4、如果identifier为一个定义了的符号,your code1就会被编译,否则your code2就会被编译。
#ifdef identifier
your code1
your code2
  5、如果epression1非零,就编译your code1,否则,如果expression2非零,就编译your code2,否则,就编译your code3。
#if expressin1
your code1
#elif expression2
your code2
your code3
  其他预编译指令除了上面我们说的集中常用的编译指令,还有3种不太常见的编译指令:#line、#error、#pragma,我们接下来就简单的谈一下。
#line的语法如下:
#line number filename
例如:#line 30 a.h
  其中,文件名a.h可以省略不写。这条指令可以改变当前的行号和文件名,例如上面的这条预处理指令就可以改变当前的行号为30,文件名是a.h。初看起来似乎没有什么用,不过,他还是有点用的,那就是用在编译器的编写中,我们知道编译器对C++源码编译过程中会产生一些中间文件,通过这条指令,可以保证文件名是固定的,不会被这些中间文件代替,有利于进行分析。
#error语法如下:
#error info
#ifndef UNIX
#error This software requires the UNIX OS.
  这条指令主要是给出错误信息,上面的这个例子就是,如果没有在UNIX环境下,就会输出This software requires the UNIX OS.然后诱发编译器终止。所以总的来说,这条指令的目的就是在程序崩溃之前能够给出一定的信息。
#pragma是非统一的,他要依靠各个编译器生产者。例如VC++中:
#pragma comment(lib,"dllTest.lib")
导入库dllTest.lib。
  八、预定义标识符
  为了处理一些有用的信息,预处理定义了一些预处理标识符,虽然各种编译器的预处理标识符不尽相同,但是他们都会处理下面的4种:
__FILE__ 正在编译的文件的名字
__LINE__ 正在编译的文件的行号
__DATE__ 编译时刻的日期字符串,例如: "25 Jan 2006"
__TIME__ 编译时刻的时间字符串,例如: "12:30:55"
  例如:cout&&"The file is :"&&__FILE__"&&"! The lines is:"&&__LINE__&&
九、预处理何去何从
  如何取代#include预处理指令,我们在这里就不再一一讨论了。C++并没有为#include提供替代形式,但是namespace提供了一种作用域机制,它能以某种方式支持组合,利用它可以改善#include的行为方式,但是我们还是无法取代#include。
  #pragma应该算是一个可有可无的预处理指令,按照C++之父Bjarne的话说,就是:&#pragma被过分的经常的用于将语言语义的变形隐藏到编译系统里,或者被用于提供带有特殊语义和笨拙语法的语言扩充。&
  对于#ifdef,我们仍然束手无策,就算是我们利用if语句和常量表达式,仍然不足以替代它,因为一个if语句的正文必须在语法上正确,满足类检查,即使他处在一个绝不会被执行的分支里面。
  十、预编译头文件的补充说明
  这里介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考:
MSDN -& Visual Studio 6.0 Document -& Visual C++ 6.0 Document -& VC++ Programmer Guider -& Compiler and Linker -& Details -& Creating Precompiled Header files
  预编译头的概念:
  所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及时清理那些没有用的预编译头文件。
  也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。
  根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次都编译那些不需要经常改变的代码。编译性能当然就提高了。
  要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个&系统级别&的,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们会发现这个头文件里包含了以下的头文件:
#include &afxwin.h&
// MFC core and standard components
#include &afxext.h&
// MFC extensions
#include &afxdisp.h&
// MFC Automation classes
#include &afxdtctl.h&
// MFC support for Internet Explorer 4 Common Controls
#include &afxcmn.h&
  这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。
  那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include &Stdafx.h&。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打开project -&Setting-&C/C++ 对话框。把Category指向Precompiled Header。在左边的树形视图里选择整个工程,如下图:
(Project -&Setting-&C/C++ 对话框)
  在图中我们的Project Options(右下角的那个白的地方)可以看到 /Fp &debug/PCH.pch&,这就是指定生成的.pch文件的名字,默认的通常是 &工程名&.pch(我的示例工程名就是PCH)。
  然后,在左边的树形视图里选择StdAfx.cpp,如图:
(Project -&Setting-&C/C++ 对话框-&StdAfx.cpp属性)
  这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。
  然后我们再选择一个其它的文件来看看,如图:
(Project -&Setting-&C/C++ 对话框-&其他文件属性)
  在这里,Precomplier 选择了 Use &&&一项,头文件是我们指定创建PCH 文件的stdafx.h文件。事实上,这里是使用工程里的设置,(如图1)/Yu "stdafx.h"。
  这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
  1)如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍是最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的,你自己试以下就知道了,绝对有很惊人的效果。
  2)如果你把pch文件不小心丢了,根据以上的分析,你只要让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)重新编译一遍就可以了。当然你可以傻傻的 Rebuild all。简单一点就是选择那个cpp文件,按一下Ctrl + F7就可以了。
预编译头文件
今天在改一个很大的程序,慢慢看,慢慢改。突然发现一个.c文件,里面什么也没有,
就几个头文件,我一看,我靠,这不是把简单的问题搞复杂了吗,随手删掉那个c文件。
结果不能编译了,我靠:
fatal error C1083: Cannot open precompiled header file: \‘Debug/v13_3.pch\‘:
No such file or directory
怎么rebuild all都不行。
上网查了一下,才搞懂了:
----------------总结------
如果工程很大,头文件很多,而有几个头文件又是经常要用的,那么
1。把这些头文件全部写到一个头文件里面去,比如写到preh.h
2。写一个preh.c,里面只一句话:#include "preh.h"
3。对于preh.c,在project setting里面设置creat precompiled headers,对于其他
.c文件,设置use precompiled header file
我试了一下,效果很明显,不用precompiled header,编译一次我可以去上个厕所,用
precompiled header,编译的时候,我可以站起来伸个懒腰,活动活动就差不多啦
---------转载的文章----------
预编译头的概念:
所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是
以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的
C/C++代码--------甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会
被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编
译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及
时清理那些没有用的预编译头文件。
也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它
只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过
的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单
位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有
头文件中的东西(.eg Macro, Preprocesser )都要重新处理一遍。VC的预编译头文件
保存的正是这部分信息。以避免每次都要重新处理这些头文件。
预编译头的作用:
根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次
都编译那些不需要经常改变的代码。编译性能当然就提高了。
预编译头的使用:
要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的
代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)
想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个&系统级别&的
,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个
典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard
会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们
会发现这个头文件里包含了以下的头文件:
#include &afxwin.h& // MFC core and standard components
#include &afxext.h& // MFC extensions
#include &afxdisp.h& // MFC Automation classes
#include &afxdtctl.h& // MFC support for Internet Explorer 4
Common Controls
#include &afxcmn.h&
这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文
件的,所以说他们是稳定的。
那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我
们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件
里只有一句代码就是:#include &Stdafx.h&。原因是理所当然的,我们仅仅是要它能
够编译而已?D?D?D也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指
定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打
开project -&Setting-&C/C++ 对话框。把Category指向Precompiled Header。在左边的
树形视图里选择整个工程 
Project Options(右下角的那个白的地方)可以看到 /Fp &debug/PCH.pch&,这就是指
定生成的.pch文件的名字,默认的通常是 &工程名&.pch(我的示例工程名就是PCH)。
然后,在左边的树形视图里选择StdAfx.cpp.//这时只能选一个cpp文件!
这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件
,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个
Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文
件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件
然后我们再选择一个其它的文件来看看,//其他cpp文件
在这里,Precomplier 选择了 Use ???一项,头文件是我们指定创建PCH 文件的stda
文件。事实上,这里是使用工程里的设置,(如图1)/Yu&stdafx.h&。
这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以
下是注意事项:
1):如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍
是最开头,包含 你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如
果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的,
你自己试以下就知道了,绝对有很惊人的效果?..
fatal error C1010: unexpected end of file while looking for precompiled
header directive
Generating Code...
2)如果你把pch文件不小心丢了,编译的时候就会产生很多的不正常的行为。根据以上
的分析,你只要让编译器生成一个pch文件。也就是说把 stdafx.cpp(即指定/Yc的那个
cpp文件)从新编译一遍。当然你可以傻傻的 Rebuild All。简单一点就是选择那个cpp
文件,按一下Ctrl + F7就可以了。不然可是很浪费时间的哦。
呵呵,如果你居然耐着性子看到了这里,那么再回到帖子最开始看看我的总结吧!
msconfig.exe
系统配置 实用程序
远程桌面连接
devmgmt.msc
设备管理器
打开记事本
services.msc
本地服务设置
任务管理器
打开资源管理器
regedit.exe
cmd命令提示符
启动计算器
compmgmt.msc
计算机管理
打开inretnet信息服务
打开控制面板
检查DirectX信息
Internet协议配置工具
inetcpl.cpl   Internet选项
appwiz.cpl   添加或删除程序
msinfo32.exe  系统信息
sysdm.cpl   系统属性
diskmgmt.msc
磁盘管理实用程序
磁盘碎片整理程序
secpol.msc
本地安全策略
音量控制程序
事件查看器
perfmon.msc
计算机性能监测程序
rononce -p
打开屏幕键盘
lusrmgr.msc
本机用户和组
剪贴板查看器
C中的预编译宏定义
2009-02-10 作者: infobillows 来源:网络
在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. 这一步骤是由预处理器(preprocessor)来完成的. 在源流程序被编译器处理之前, 预处理器首先对源程序中的"宏(macro)"进行处理.
C初学者可能对预处理器没什么概念, 这是情有可原的: 一般的C编译器都将预处理, 汇编, 编译, 连接过程集成到一起了. 编译预处理往往在后台运行. 在有的C编译器中, 这些过程统统由一个单独的程序来完成, 编译的不同阶段实现这些不同的功能. 可以指定相应的命令选项来执行这些功能. 有的C编译器使用分别的程序来完成这些步骤. 可单独调用这些程序来完成. 在gcc中, 进行编译预处理的程序被称为CPP, 它的可执行文件名为cpp.
编译预处理命令的语法与C语言的语法是完全独立的. 比如: 你可以将一个宏扩展为与C语法格格不入的内容, 但该内容与后面的语句结合在一个若能生成合法的C语句, 也是可以正确编译的.
(一) 预处理命令简介
预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:
定义一个预处理宏
取消宏的定义
包含文件命令
#include_next
与#include相似, 但它有着特殊的用途
编译预处理中的条件命令, 相当于C语法中的if语句
判断某个宏是否被定义, 若已定义, 执行随后的语句
与#ifdef相反, 判断某个宏是否未被定义
若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if
与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else
#if, #ifdef, #ifndef这些条件命令的结束标志.
与#if, #elif配合使用, 判断某个宏是否被定义
标志该语句所在的行号
将宏参数替代为以参数值为内容的字符窜常量
将两个相邻的标记(token)连接为一个单独的标记
说明编译器信息
显示编译警告信息
显示编译错误信息
(二) 预处理的文法
预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.
预处理语句格式:
#command name(...) token(s)
1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.
2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).
3, 语句中可以利用"\"来换行.
ONE 1 /* ONE == 1 */
等价于: #define ONE 1
#define err(flag, msg) if(flag) \
printf(msg)
等价于: #define err(flag, msg) if(flag) printf(msg)
(三) 预处理命令详述
1, #define
#define命令定义一个宏:
#define MACRO_NAME(args) tokens(opt)
之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.
不带参数的宏被称为"对象宏(objectlike macro)"
#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.
#define MAX 100
int a[MAX];
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.
要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.
带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.
函数宏的参数是固定的情况
函数宏的定义采用这样的方式: #define name( args ) tokens
其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.
注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为 以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.
#define mul(x,y) ((x)*(y))
注意, 函数宏之后的参数要用括号括起来, 看看这个例子:
#define mul(x,y) x*y
"mul(1, 2+2);" 将被扩展为: 1*2 + 2
同样, 整个标记串也应该用括号引用起来:
#define mul(x,y) (x)*(y)
sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0
调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:
mul (f(a,b), g(c,d));
#define insert(stmt) stmt
insert ( a=1; b=2;)
相当于在代码中加入 a=1; b=2 .
insert ( a=1, b=2;)
就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.
insert ((a=1, b=2;)) 可解决上述问题.
在定义和调用函数宏时候, 要注意一些问题:
1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题.
example_3.7:
#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}
如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp};
明显后面的;是多余的, 我们应该这样调用: swap(1,2)
虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:
#define swap(x,y)
do { unsigned long _temp=x; x=y; y=_tmp} while (0)
swap(1,2); 将被替换为:
do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);
在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.
2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.
#define incr(v, low, high)
for ((v) = (low),; (v) &= (high); (v)++)
只能以这样的形式被调用: incr(a, 1, 10)
/* increase a form 1 to 10 */
函数宏中的参数包括可变参数列表的情况
C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.
#define name(args, ...) tokens
#define name(...) tokens
"..."代表可变参数列表, 如果它不是仅有的参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃).
通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.
#ifdef DEBUG
#define my_printf(...) fprintf(stderr, __VA_ARGS__)
#define my_printf(...) printf(__VA_ARGS__)
tokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.
注意在使用#define时候的一些常见错误:
#define MAX = 100
#define MAX 100;
=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";".
注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍.
关于定义宏的另外一些问题
(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...
#define NULL 0
#define NULL /* null pointer */
上面的重定义是相同的, 但下面的重定义不同:
#define fun(x) x+1
#define fun(x) x + 1 或: #define fun(y) y+1
如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.
应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.
(2) 在gcc中, 可在命令行中指定对象宏的定义:
$ gcc -Wall -DMAX=100 -o tmp tmp.c
相当于在tmp.c中添加" #define MAX 100".
那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?
---若-DMAX=1, 则正确编译.
---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.
注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1
(3) #define所定义的宏的作用域
宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别
#define ONE 1
sum = ONE + TWO
/* sum = 1 + TWO
#define TWO 2
sum = ONE + TWO
/* sum = 1 + 2
#undef ONE
sum = ONE + TWO
/* sum = ONE + 2
char c[] = "TWO"
/* c[] = "TWO", NOT "2"! */
(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.
# define ONE NUMBER_1
# define NUMBER_1 1
int a = ONE
/* a = 1 */
#undef用来取消宏定义, 它与#define对立:
#undef name
如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误.
当一个宏定义被取消后, 可以再度定义它.
3, #if, #elif, #else, #endif
#if, #elif, #else, #endif用于条件编译:
#if 常量表达式1
#elif 常量表达式2
#elif 常量表达式3
#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.
else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.
使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.
一大段代码;
常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.
#if MACRO_NON_DEFINED
在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.
注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.
4, #ifdef, #ifndef, defined.
#ifdef, #ifndef, defined用来测试某个宏是否被定义
#ifdef name
或 #ifndef name
它们经常用于避免头文件的重复引用:
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
defined(name): 若宏被定义,则返回1, 否则返回0.
它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:
#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.
5, #include , #include_next
#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.
#include "headfile"
#include &headfile&
#include 预处理标记
前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.
实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include &headfile&包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.
关于#include "headfile"和#include &headfile&的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.
相对于#include, 我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.
比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.
可参考cpp手册进一步了解#include_next
6, 预定义宏
标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.
下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:
当前语句所在的行号, 以10进制整数标注.
当前源文件的文件名, 以字符串常量标注.
程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.
程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.
如果当前编译器符合ISO标准, 那么该宏的值为1
__STDC_VERSION__
如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L.
我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢?
__STDC_HOSTED__
如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.
gcc定义的预定义宏:
__OPTMIZE__
如果编译过程中使用了优化, 那么该宏被定义为1.
__OPTMIZE_SIZE__
同上, 但仅在优化是针对代码大小而非速度时才被定义为1.
__VERSION__
显示所用gcc的版本号.
可参考"GCC the complete reference".
要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null
#line用来修改__LINE__和__FILE__.
printf("line: %d, file: %s\n", __LINE__, __FILE__);
#line 100 "haha"
printf("line: %d, file: %s\n", __LINE__, __FILE__);
printf("line: %d, file: %s\n", __LINE__, __FILE__);
line: 34, file: 1.c
line: 100, file: haha
line: 101, file: haha
8, #pragma, _Pragma
#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:
#pragma GCC name token(s)
#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.
(1) #pragma GCC dependency
dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.
在demo.c中给出这样一句:
#pragma GCC dependency "temp-file"
然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息:
warning: current file is older than temp-file
如果当前文件比指定的文件新, 则不给出任何警告信息.
还可以在在#pragma中给添加自定义的警告信息.
#pragma GCC dependency "temp-file" "demo.c needs to be updated!"
1.c:27:38: warning: extra tokens at end of #pragma directive
1.c:27:38: warning: current file is older than temp-file
注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.
(2) #pragma GCC poison token(s)
若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息.
#pragma GCC poison scanf
scanf("%d", &a);
warning: extra tokens at end of #pragma directive
error: attempt to use poisoned "scanf"
注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.
(3) #pragma GCC system_header
从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明).
(这条#pragma语句还没发现用什么大的用处)
由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma:
#define PRAGMA_DEP #pragma GCC dependency "temp-file"
由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:
#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")
注意, ()中包含的""引用之前引该加上\转义字符.
#和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.
#用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.
#define TEST(a,b) printf( #a "&" #b "=%d\n", (a)&(b));
注意: #只针对紧随其后的token有效!
##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token.
#define TYPE(type, n) type n
TYPE(int, a) = 1;
TYPE(long, b) = 1999;
将被替换为:
int a = 1;
long b = 1999;
(10) #warning, #error
#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:
#warning tokens
#error tokens
#warning "some warning"
注意, #error和#warning后的token要用""引用起来!
(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.
一.预处理的工作方式... 3
1.1.预处理的功能... 3
1.2预处理的工作方式... 3
二.预处理指令... 4
2.1.预处理指令... 4
2.2.指令规则... 4
三.宏定义命令----#define. 4
3.1.无参数的宏... 4
3.2带参数的宏... 5
3.3.预处理操作符#和##. 6
3.3.1.操作符#. 6
3.3.2.操作符##. 6
四.文件包含------include. 6
五.条件编译... 7
5.1使用#if 7
5.2使用#ifdef和#ifndef 9
5.3使用#defined和#undef 10
六.其他预处理命令... 11
6.1.预定义的宏名... 11
6.2.重置行号和文件名命令------------#line. 11
6.3.修改编译器设置命令 ------------#pragma. 12
6.4.产生错误信息命令 ------------#error 12
七.内联函数... 13
在嵌入式系统编程中不管是内核的驱动程序还是应用程序的编写,涉及到大量的预处理与条件编译,这样做的好处主要体现在代码的移植性强以及代码的修改方便等方面。因此引入了预处理与条件编译的概念。
在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。
一.预处理的工作方式
1.1.预处理的功能
在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:
●将源文件中以&include&格式包含的文件复制到编译的源文件中。
●用实际值替换用&#define&定义的字符串。
●根据&#if&后面的条件决定需要编译的代码。
1.2预处理的工作方式
预处理的行为是由指令控制的。这些指令是由#字符开头的一些命令。
#define指令定义了一个宏---用来代表其他东西的一个命令,通常是某一个类型的常量。预处理会通过将宏的名字和它的定义存储在一起来响应#define指令。当这个宏在后面的程序中使用到时,预处理器&扩展&了宏,将宏替换为它所定义的值。
#include指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分&包含&进来。例如:下面这行命令:
#include&stdio.h&
指示预处理器打开一个名字为stdio.h的文件,并将它的内容加到当前的程序中。
预处理器的输入是一个C语言程序,程序可能包含指令。预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出是另外一个程序:原程序的一个编辑后的版本,不再包含指令。预处理器的输出被直接交给编译器,编译器检查程序是否有错误,并经程序翻译为目标代码。
二.预处理指令
2.1.预处理指令
大多数预处理器指令属于下面3种类型:
●宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。
●文件包含:#include指令导致一个指定文件的内容被包含到程序中。
●条件编译:#if,#ifdef,#ifndef,#elif,#else和#dendif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。
剩下的#error,#line和#pragma指令更特殊的指令,较少用到。
2.2.指令规则
●指令都是以#开始。#符号不需要在一行的行首,只要她之前有空白字符就行。在#后是指令名,接着是指令所需要的其他信息。
●在指令的符号之间可以插入任意数量的空格或横向制表符。
●指令总是第一个换行符处结束,除非明确地指明要继续。
●指令可以出现在程序中德任何地方。我们通常将#define和#include指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。
●注释可以与指令放在同一行。
三.宏定义命令----#define
使用#define命令并不是真正的定义符号常量,而是定义一个可以替换的宏。被定义为宏的标示符称为&宏名&。在编译预处理过程时,对程序中所有出现的&宏名&,都用宏定义中的字符串去代换,这称为&宏代换&或&宏展开&。
在C语言中,宏分为有参数和无参数两种。
3.1.无参数的宏
其定义格式如下:
#define 宏名
在以上宏定义语句中,各部分的含义如下:
● #:表示这是一条预处理命令(凡是以&#&开始的均为预处理命令)。
●define:关键字&define&为宏定义命令。
●宏名:是一个标示符,必须符合C语言标示符的规定,一般以大写字母标示宏名。
●字符串:可以是常数,表达式,格式串等。在前面使用的符号常量的定义就是一个无参数宏定义。
预处理命令语句后面一般不会添加分号,如果在#define最后有分号,在宏替换时分号也将替换到源代码中去。在宏名和字符串之间可以有任意个空格。
Eg:#define PI 3.14
在使用宏定义时,还需要注意以下几点:
●宏定义是宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。
●宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。
●宏名在源程序只能够若用引号括起来,则预处理程序不对其作宏替换。
●宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层替换。
●习惯上宏名可用大写字母表示,以方便与变量区别。但也允许用小写字母。
3.2带参数的宏
#define命令定义宏时,还可以为宏设置参数。与函数中的参数类似,在宏定于中的参数为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,还要用实参去代换形参。
带参宏定义的一般形式为:
#define 宏名(形参表)
在定义带参数的宏时,宏名和形参表之间不能有空格出现,否则,就将宏定义成为无参数形式,而导致程序出错。
Eg:#define ABS(x) (x)&0?-(x):(x)
以上的宏定义中,如果x的值小于0,则使用一元运算符(-)对其取负,得到正数。
带参的宏和带参的函数相似,但其本质是不同的。使用带参宏时,在预处理时将程序源代码替换到相应的位置,编译时得到完整的目标代码,而不进行函数调用,因此程序执行效率要高些。而函数调用只需要编译一次函数,代码量较少,一般情况下,对于简单的功能,可使用宏替换的形式来使用。
3.3.预处理操作符#和##
3.3.1.操作符#
在使用#define定义宏时,可使用操作符#在字符串中输出实参。Eg:
#define AREA(x,y) printf(&长为&#x&,宽为&#y&的长方形的面积:%d\n&,(x)*(y));
3.3.2.操作符##
与操作符#类似,操作符##也可用在带参宏中替换部分内容。该操作符将宏中的两个部分连接成一个内容。例如,定义如下宏:
#define VAR(n)
当使用一下方式引用宏:
预处理时,将得到以下形式:
如果使用以下宏定义:
#define FUNC(n)
当实参为1时,预处理后得到一下形式:
四.文件包含------include
当一个C语言程序由多个文件模块组成时,主模块中一般包含main函数和一些当前程序专用的函数。程序从main函数开始执行,在执行过程中,可调用当前文件中的函数,也可调用其他文件模块中的函数。
如果在模块中要调用其他文件模块中的函数,首先必须在主模块中声明该函数原型。一般都是采用文件包含的方法,包含其他文件模块的头文件。
文件包含中指定的文件名即可以用引号括起来,也可以用尖括号括起来,格式如下:
#include& 文件名&
#include&文件名&
如果使用尖括号&&括起文件名,则编译程序将到C语言开发环境中设置好的 include文件中去找指定的文件。
因为C语言的标准头文件都存放在include文件夹中,所以一般对标准头文件采用尖括号;对编程自己编写的文件,则使用双引号。如果自己编写的文件不是存放在当前工作文件夹,可以在#include命令后面加在路径。
#include命令的作用是把指定的文件模块内容插入到#include所在的位置,当程序编译链接时,系统会把所有#include指定的文件链接生成可执行代码。文件包含必须以#开头,表示这是编译预处理命令,行尾不能用分号结束。
#include所包含的文件,其扩展名可以是&.c&,表示包含普通C语言源程序。也可以是 &.h&,表示C语言程序的头文件。C语言系统中大量的定义与声明是以头文件形式提供的。
通过#define包含进来的文件模块中还可以再包含其他文件,这种用法称为嵌套包含。嵌套的层数与具体C语言系统有关,但是一般可以嵌套8层以上。
五.条件编译
预处理器还提供了条件编译功能。在预处理时,按照不同的条件去编译程序的不同部分,从而得到不同的目标代码。使用条件编译,可方便地处理程序的调试版本和正式版本,也可使用条件编译使程序的移植更方便。
5.1使用#if
与C语言的条件分支语句类似,在预处理时,也可以使用分支,根据不同的情况编译不同的源代码段。
#if 的使用格式如下:
#if 常量表达式
该条件编译命令的执行过程为:若常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。
#define DEBUG 1
int main()
char ch[26];
for(i=‘a‘;j=0;i&=‘z‘;i++,j++)
printf("ch[%d]=%c\n",j,ch[j]);
for(j=0;j&26;j++)
printf("%c",ch[j]);
#if预编译命令还可使用多分支语句格式,具体格式如下:
#if 常量表达式 1
#elif 常量表达式 2
#elif 常量表达式 n
关键字#elif与多分支if语句中的else if类似。
#define os win
#if os=win
#include"win.h"
#elif os=linux
#include"linux.h"
#elif os=mac
#include"mac.h"
#if和#elif还可以进行嵌套,C89标准中,嵌套深度可以到达8层,而C99允许嵌套达到63层。在嵌套时,每个#endif,#else或#elif与最近的#if或#elif配对。
#define MAX 100
#define OLD -1
int main()
#if MAX&50
#elif OLD&0
#elif OLD&4
5.2使用#ifdef和#ifndef
在上面的#if条件编译命令中,需要判断符号常量定义的具体值。在很多情况下,其实不需要判断符号常量的值,只需要判断是否定义了该符号常量。这时,可不使用#if命令,而使用另外一个预编译命令&&&#ifdef.
#ifdef命令的使用格式如下:
#ifdef 标识符
其意义是,如果#ifdef后面的标识符已被定义过,则对&程序段1&进行编译;如果没有定义标识符,则编译&程序段2&。一般不使用#else及后面的&程序2&。
而#ifndef的意义与#ifdef相反,其格式如下:
#ifndef 标识符
其意义是:如果未定义标识符,则编译&程序段1&;否则编译&程序段2&。
5.3使用#defined和#undef
与#ifdef类似的,可以在#if命令中使用define来判断是否已定义指定的标识符。例如:
#if defined 标识符
与下面的标示方式意义相同。
#ifdef 标识符
也可使用逻辑运算符,对defined取反。例如:
#if ! define 标识符
与下面的标示方式意义相同。
#ifndef 标识符
在#ifdef和#ifndef命令后面的标识符是使用#define进行定义的。在程序中,还可以使用#undef取消对标识符的定义,其形式为:
#undef 标识符
#define MAX 100
#undef MAX
在以上代码中,首先使用#define定义标识符MAX,经过一段程序代码后,又可以使用#undef取消已定义的标识符。使用#undef命令后,再使用#ifdef max,将不会编译后的源代码,因为此时标识符MAX已经被取消定义了。
六.其他预处理命令
6.1.预定义的宏名
ANSI C标准预定义了五个宏名,每个宏名的前后均有两个下画线,避免与程序员定义相同的宏名(一般都不会定义前后有两个下划线的宏)。这5个宏名如下:
__DATE__:当前源程序的创建日期。
__FILE__:当前源程序的文件名称(包括盘符和路径)。
__LINE__:当前被编译代码的行号。
__STDC__:返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C.
● __TIME__:当前源程序的创建时间。
#include&stdio.h&
int main()
printf("日期:%s\n",__DATE__);
printf("时间:%s\n",__TIME__};
printf("文件名:%s\n",__FILE__);
printf("这是第%d行代码\n",__LINE__);
printf("本编译器%s标准C\n",(__STD__)?"符合":"不符合");
6.2.重置行号和文件名命令------------#line
使用__LINE__预定义宏名赈灾编译的程序行号。使用#line命令可改变预定义宏__LINE__与__FILE__的内容,该命令的基本形如下:
#line number[&filename&]
其中的数字为一个正整数,可选的文件名为有效文件标识符。行号为源代码中当前行号,文件名为源文件的名字。命令为#line主要用于调试以及其他特殊应用。
1:#include&stdio.h&
2:#include&stdlib.h&
4:#line 1000
6:int main()
printf("当前行号:%d\n",__LINE__);
在以上程序中,在第4行中使用#line定义的行号为从1000开始(不包括#line这行)。所以第5行的编号将为1000,第6行为1001,第7行为1002,第8行为1003.
6.3.修改编译器设置命令 ------------#pragma
#pragma命令的作用是设定编译器的状态,或者指示编译器完全一些特定的动作。#pragma命令对每个编译器给出了一个方法,在保持与C语言完全兼容的情况下,给出主机或者操作系统专有的特征。其格式一般为:
#pragma Para
其中,Para为参数,可使用的参数很多,下面列出常用的参数:
Message参数,该参数能够在编译信息输出窗口中输出对应的信息,这对于源代码信息的控制是非常重要的,其使用方法是:
#pragma message(消息文本)
当编译器遇到这条指令时,就在编译输出窗口中将消息文本显示出来。
另外一个使用比较多得pragma参数是code_seg.格式如:
#pragma code_seg([&section_name&[,section_class]])
它能够设置程序中函数代码存放的代码段,在开发驱动程序的时候就会使用到它。
参数once,可保证头文件被编译一次,其格式为:
#pragma once
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
6.4.产生错误信息命令 ------------#error
#error命令强制编译器停止编译,并输出一个错误信息,主要用于程序调试。其使用如下:
#error 信息错误
注意,错误信息不用双括号括起来。当遇到#error命令时,错误信息将显示出来。
例如,以下编译预处理器命令判断预定义宏__STDC__,如果其值不为1,则显示一个错误信息,提示程序员该编译器不支持ANSI C标准。
#if __STDC__!=1
#error NOT ANSI C
七.内联函数
在使用#define定义带参数宏时,在调用函数时,一般需要增加系统的开销,如参数传递,跳转控制,返回结果等额外操作需要系统内存和执行时间。而使用带参数宏时,通过宏替换可再编译前将函数代码展开导源代码中,使编译后的目标文件含有多段重复的代码。这样做,会增加程序的代码量,都可以减少执行时间。
在C99标准钟,还提供另外一种解决方法:使用内联函数。
在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替代。显然,这种做法不会产生转去转回得问题。都是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标代码量,进而增加空间的开销,而在时间开销上不像函数调用时那么大,可见它是以增加目标代码为代码来换取时间的节省。
定义内联函数的方法很简单,只要在定义函数头的前面加上关键字inline即可。内联函数的定义与一般函数一样。例如,定于一个两个整数相加的函数:
#include&stdio.h&
#include&stdlib.h&
inline int add(int x,int y);
inline int add(int x,int y)
return x+y;
int main()
int i,j,k;
printf("请输入两个整数的值:\n");
scanf("%d %d",&i,&j);
k=add(i,j);
printf("k=%d\n",k);
在程序中,调用函数add时,该函数在编译时会将以上代码复制过来,而不是像一般函数那样是运行时被调用。
内联函数具有一般函数的特性,它与一般函数所不同之处在于函数调用的处理。一般函数进行调用时,要讲程序执行权转导被调函数中,然后再返回到调用到它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应该注意如下几点:
在内联函数内部允许用循环语句和开关语句。
内联函数的定义必须出现在内联函数第一次被调用之前。
其实,在程序中声明一个函数为内联时,编译以后这个函数不一定是内联的,
即程序只是建议编译器使用内联函数,但是编译器会根据函数情况决定是否使用内联,所以如果编写的内联函数中出现循环或者开关语句,程序也不会提示出错,但那个函数已经不是内联函数了。
一般都是讲一个小型函数作为内联函数。
C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境。本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性。ANSI标准定义的C语言预处理程序包括下列命令:
#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。非常明显,所有预处理命令均以符号#开头,下面分别加以介绍。
一 #define
命令#define定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的串代换它。ANSI标准将标识符定义为宏名,将替换过程称为宏替换。命令的一般形式为:
#define identifier string
1该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新行结束。
2宏名定义后,即可成为其它宏名定义中的一部分。
3 宏替换仅仅是以文本串代替宏标识符,前提是宏标识符必须独立的识别出来,否则不进行替换。例如:
#define XYZ this is a tes
使用宏printf("XYZ");//该段不打印"this is a test"而打印"XYZ"。因为预编译器识别出的是"XYZ"
4如果串长于一行,可以在该行末尾用一反斜杠‘ \‘续行。
#defineLONG_STRING"this is a very long\
string that is used as an example"
5 C语言程序普遍使用大写字母定义标识符。
6 用宏代换代替实在的函数的一大好处是宏替换增加了代码的速度,因为不存在函数调用的开销。但增加速度也有代价:由于重复编码而增加了程序长度。
命令#error强迫编译程序停止编译,主要用于程序调试。
#error指令使预处理器发出一条错误消息,该消息包含指令中的文本.这条指令的目的就是在程序崩溃之前能够给出一定的信息。
三 #include
命令#i nclude使编译程序将另一源文件嵌入带有#include的源文件,被读入的源文件必须用双引号或尖括号括起来。例如:
#include"stdio.h"或者#include&stdio.h&
这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。
将文件嵌入#i nclude命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次依赖于具体实现。
如果显式路径名为文件标识符的一部分,则仅在那些子目录中搜索被嵌入文件。否则,如果文件名用双引号括起来,则首先检索当前工作目录。如果未发现文件,则在命令行中说明的所有目录中搜索。如果仍未发现文件,则搜索实现时定义}

我要回帖

更多关于 路径中具有非法字符 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信