C语言的本质(21)——预处理之三:其它预处理特性及总结
C标准规定了几个特殊的宏,在不同的地方使用可以自动展开成不同的值,预编译程序对于在源程序中出现的这些串将用合适的值进行替换。这些宏有下面这些:
__FILE__ 展开为当前源文件的文件名,是一个字符串
__LINE__ 展开为当前代码行的行号,是一个整数
__DATE__ 展开为包含当前日期的字符串
__STDC__ 如果编译器遵循ANSIC标准,它就是个非零值
__TIME__ 展开为包含当前时间的字符串
注意:是双下划线,而不是单下划线。
常用的有__FILE__和__LINE__这两个宏在源代码中不同的位置使用会自动取不同的值,显然不是用#define能定义得出来的,它们是编译器内建的特殊的宏。在打印调试信息时打印这两个宏可以给开发者非常有用的提示。
#include<stdio.h>
int main(void)
{
printf("HelloWorld!\n");
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
return 0;
}
下面我们自己实现断言assert函数,以理解它的原理。
/* assert.h standard header */
#undef assert /* remove existing definition */ #ifdef NDEBUG
#defineassert(test) ((void)0)
#else /*NDEBUG not defined */
void_Assert(char *);
/*macros */
#define_STR(x) _VAL(x)
#define_VAL(x) #x
#defineassert(test) ((test) ? (void)0 \
:_Assert(__FILE__ ":" _STR(__LINE__) " " #test))
#endif
C标准规定assert应该实现为宏定义而不是一个真正的函数,并且assert(test)这个表达式的值应该是void类型的。首先用#undef assert确保取消前面对assert的定义,然后分两种情况:如果定义了NDEBUG,那么assert(test)直接定义成一个void类型的值,什么也不做;如果没有定义NDEBUG,则要判断测试条件test是否成立,如果条件成立就什么也不做,如果不成立则调用_Assert函数。假设在main.c文件的第33行调用assert(is_sorted()),那么__FILE__是字符串"main.c",__LINE__是整数33,#test是字符串"is_sorted()"。注意_STR(__LINE__)的展开过程:首先展开成_VAL(33),然后进一步展开成字符串"33"。这样,最后_Assert调用的形式是_Assert("main.c"":" "33" " " "is_sorted()"),传给_Assert函数的字符串是"main.c:33is_sorted()"。_Assert函数是我们自己定义的,在另一个源文件中:
/* xassert.c _Assert function */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h> void _Assert(char *mesg)
{ /*print assertion message and abort */
fputs(mesg,stderr);
fputs("-- assertion failed\n", stderr);
abort();
}
注意,在头文件assert.h中自己定义的内部使用的标识符都以_线开头,例如_STR,_VAL,_Assert,因为我们在模拟C标准库的实现,以_线开头的标识符通常由编译器和C语言库使用,在/usr/include下的头文件中你可以看到大量_线开头的标识符。另外为什么我们不直接在assert的宏定义中调用fputs和abort呢?因为调用这两个函数需要包含stdio.h和stdlib.h,C标准库的头文件应该是相互独立的,一个程序只要包含assert.h就应该能使用assert,而不应该再依赖于别的头文件。_Assert中的fputs向标准错误输出打印错误信息,abort异常终止当前进程。
现在测试一下我们的assert实现,把assert.h和xassert.c和测试代码main.c放在同一个目录下。
/* main.c */
#include "assert.h" int main(void)
{
assert(2>3);
return0;
}
注意#include "assert.h"要用"引号而不要用<>括号,以保证包含的是我们自己写的assert.h而非C标准库的头文件。然后编译运行:
$ gcc main.c xassert.c
$ ./a.out
main.c:6 2>3 -- assertion failed
Aborted
#error指令将使编译器显示一条错误信息,然后停止编译。
#line指令改变_LINE_与_FILE_的内容,它们是在编译程序中预先定义的标识符。
#line举例:
#line 100 //初始化行计数器
#include<stdio.h> //行号100
int main(void)
{
    printf("HelloWorld!\n");
    printf("%d",__LINE__);
    return 0;
}
输出104
#pragma指令没有正式的定义。它预处理指示供编译器实现一些非标准的特性,C标准没有规定#pragma后面应该写什么以及起什么作用,由编译器自己规定。典型的用法是禁止或允许某些烦人的警告信息。有的编译器用#pragma定义一些特殊功能寄存器名,有的编译器用#pragma定位链接地址。如果编译器在代码中碰到不认识的#pragma指示则忽略它,例如gcc的#pragma指示都是#pragma GCC ...这种形式,用别的编译器编译则忽略这些指示。
预处理指令总结:
预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。
宏定义可以带有参数,宏调用时是以实参代换形参。而不是“值传送”。
为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。
文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。
使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。
C语言的本质(21)——预处理之三:其它预处理特性及总结的更多相关文章
- C语言的本质(4)——浮点数的本质与运算
		
C语言的本质(4)--浮点数的本质与运算 C语言规定了3种浮点数,float型.double型和long double型,其中float型占4个字节,double型占8个字节,longdouble型长 ...
 - C语言的本质(28)——C语言与汇编之用汇编写一个Helloword
		
为了更加深入理解C语言的本质,我们需要学习一些汇编相关的知识.作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但是非常重要.因为它能够完成许多其它语言所无法完成的功能.就拿 Linux 内核 ...
 - C语言的本质(15)——C语言的函数接口入门
		
C语言的本质(15)--C语言的函数接口 函数的调用者和其实现者之间存在一个协议,在调用函数之前,调用者要为实现者提供某些条件,在函数返回时,实现者完成调用者需要的功能. 函数接口通过函数名,参数和返 ...
 - C语言的本质(7)——C语言运算符大全
		
C语言的本质(7)--C语言运算符大全 C语言的结合方向 C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左).例如算术运算符的结合性是自左至右,即先左后右.如有表达式 x- ...
 - C语言的本质(3)——整数的本质与运算
		
C语言的本质(3)--整数的本质与运算 计算机存储的最小单位是字节(Byte),一个字节通常是8个bit.C语言规定char型占一个字节的存储空间.如果这8个bit按无符号整数来解释,则取值范围是0~ ...
 - C语言入门(21)——使用DBG对C语言进行调试
		
C语言入门(21)--使用DBG对C语言进行调试 程序中除了一目了然的Bug之外都需要一定的调试手段来分析到底错在哪.到目前为止我们的调试手段只有一种:根据程序执行时的出错现象假设错误原因,然后在代码 ...
 - Docs-.NET-C#-指南-语言参考-预处理器指令:#if 预处理指令
		
ylbtech-Docs-.NET-C#-指南-语言参考-预处理器指令:#if 预处理指令 1.返回顶部 1. #if(C# 参考) 2018/06/30 如果 C# 编译器遇到 #if 指令,最终是 ...
 - Mysql 预处理 PREPARE以及预处理的好处
		
Mysql 预处理 PREPARE以及预处理的好处 Mysql手册 预处理记载: 预制语句的SQL语法在以下情况下使用: · 在编代码前,您想要测试预制语句在您的应用程序中运行得如何.或者也许一个 ...
 - 【转】assert预处理宏与预处理变量
		
assert assert是一个预处理宏,由预处理器管理而非编译器管理,所以使用时都不用命名空间声明,如果你写成std::assert反而是错的.使用assert需要包含cassert或assert. ...
 - atitit.跨语言实现备份mysql数据库 为sql文件特性 api 兼容性java c#.net php js
		
atitit.跨语言实现备份mysql数据库 为sql文件特性 api 兼容性java c#.net php js 1. 两个方法:: bat vs mysqldump(推荐) vs lang ...
 
随机推荐
- Clojure操作mysql
			
在Eclipse中新建一个Clojure工程clj01 clojure 操作mysql需要依赖mysql-connector-java.clojure-contrib与java.jdbc三个jar包. ...
 - Qt 自定义事件详细实例(继承QEvent,然后QCoreApplication::postEvent()、sendEvent())
			
创建用户事件 创建一个自定义类型的事件,首先需要有一个事件号,其值通常大于QEvent::User.为了传递事件信息,因此必须编写自定义的事件类,该事件类从QEvent继承. 编写用户事件:编写用户事 ...
 - java没有条件编译
			
摘自http://maosidiaoxian.iteye.com/blog/1290740 条件编译绝对是一个好东西.如在C或CPP中,可以通过预处理语句来实现条件编译.代码如下: #IFDEF DE ...
 - hdu 3954 Level up(线段树)
			
题目链接:hdu 3954 Level up 题目大意:N个英雄,M个等级,初始等级为1,给定每一个等级须要的经验值,Q次操作,操作分两种,W l r x:表示l~r之间的英雄每一个人杀了x个怪物:Q ...
 - linux内存操作----kernel 3.5.X    copy_from_user()和copy_to_user()
			
前面的一篇文章中简单的描写叙述了一下内存映射的内容,http://blog.csdn.net/codectq/article/details/25658813,这篇文章作为用户把内存规划好之后,在用户 ...
 - visual studio 2010 C语言声明异常
			
如下这段程序,是C_Primer_plus_第五版内的一个复习题答案(感觉声明i的值有问题),在GCC上面可以运行,但是移植到VS2010就一堆错误, #include<stdio.h> ...
 - hibernate某些版本(4.3)下报错 NoSuchMethodError: javax.persistence.Table.indexes()
			
其实本来没啥大问题,但到网上查的时候发现了一些误人子弟的说法,所以还是记下来吧. 现象: hibernate从低版本升级到某一个版本时(我们是升到4.3.10)时,在程序启动时会报错: java.la ...
 - 创建httpd启动脚本并加入启动列表开机启动
			
第一步: cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd 第二步: vim /etc/init.d/httpd 在第一行#!/bin/s ...
 - 解决 Visual Studio 2012 有时不能调试的问题
			
有时候发现 Visual Studio 2012 不能调试,有时候又能调试.感觉很烦,今天找到了一个解决办法,我也不知道为什么这样能解决. 问题: 解决:1. 找到 Properties ,双击 2. ...
 - 「前端开发者」如何把握住「微信小程序」这波红利?
			
由于前两周一直在老家处理重要事情,虽然朋友圈被「微信小程序」刷爆了,但并没有时间深入了解. 昨天回广州之后,第一件事情就是把「微信小程序」相关的文章.开发文档.设计规范全部看了一遍,基本上明白了「微信 ...