C 库宏 - assert() 源码
assert.h
/* assert.h - ANSI standard assert functions header */
/* Copyright 1992 Wind River Systems, Inc. */
/*
modification history
--------------------
01e,13nov92,smb fixed assert macro to not generate warnings
01d,22sep92,rrr added support for c++
01c,20jul92,smb added __assert extern.
01b,04jul92,jcf cleaned up.
01a,03jul92,smb written.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "types/vxANSI.h"
#undef assert
#ifdef NDEBUG
#define assert(ignore) ((void) 0)
#else /* turn debugging on */
#define _ASSERT_STR(z) _ASSERT_TMP(z)
#define _ASSERT_TMP(z) #z
#if defined(__STDC__) || defined(__cplusplus)
extern void __assert (const char *msg);
#else
extern void __assert ();
#endif
#define assert(test) ((void) \
((test) ? ((void) 0) : \
__assert("Assertion failed: "#test", file " \
__FILE__ ", line "_ASSERT_STR(__LINE__)"\n")))
#endif /* NDEBUG */
#ifdef __cplusplus
}
#endif
assert.c
/* assert.c - ANSI standard assert function */
/* Copyright 1992 Wind River Systems, Inc. */
/*
modification history
--------------------
01c,24oct92,smb removed some redundant documentation.
01b,20sep92,smb documentation additions.
01a,20jul92,smb written.
*/
/*
DESCRIPTION
INCLUDE FILES: stdio.h, stdlib.h, assert.h
SEE ALSO: American National Standard X3.159-1989
NOMANUAL
*/
#include "vxWorks.h"
#include "assert.h"
#include "stdio.h"
#include "stdlib.h"
/******************************************************************************
*
* __assert - function called by the assert macro.
*
* INCLUDE: stdio.h assert.h
*
* RETURNS: never returns
* NOMANUAL
*/
void __assert
(
const char *msg /* message string */
)
{
fdprintf(2, "%s\n", CHAR_FROM_CONST (msg)); /* print msg to error stream */
abort();
}
主要代码为:
#define assert(test) ((void) \
((test) ? ((void) 0) : \
__assert("Assertion failed: "#test", file " \
__FILE__ ", line "_ASSERT_STR(__LINE__)"\n")))
assert是一个宏定义,在这个宏定义中调用了一个函数 assert()。
assert(expression)中expression为真时,不发生什么;当expression为假时则调用assert()函数;
__assert()函数调用了fdprintf()函数和abort()函数。fdprintf()函数用来打印信息,abort()函数用来终止程序执行。
/*******************************************************************************
*
* fdprintf - write a formatted string to a file descriptor
*
* This routine writes a formatted string to a specified file descriptor. Its
* function and syntax are otherwise identical to printf().
*
* RETURNS: The number of characters output, or ERROR if there is an error
* during output.
*
* SEE ALSO: printf()
*
* VARARGS2
*/
int fdprintf
(
int fd, /* file descriptor to write to */
const char * fmt, /* format string to write */
... /* optional arguments to format */
)
{
va_list vaList; /* traverses argument list */
int nChars;
va_start (vaList, fmt);
nChars = fioFormatV (fmt, vaList, printbuf, fd);
va_end (vaList);
return (nChars);
}
void abort (void)
{
raise (SIGABRT);
exit (EXIT_FAILURE);
}
/*******************************************************************************
*
* raise - send a signal to the caller's task
*
* This routine sends the signal <signo> to the task invoking the call.
*
* RETURNS: OK (0), or ERROR (-1) if the signal number or task ID is invalid.
*
* ERRNO: EINVAL
*/
int raise
(
int signo /* signal to send to caller's task */
)
{
return (kill ((int) taskIdCurrent, signo));
}
/*******************************************************************************
*
* exit - exit a task (ANSI)
*
* This routine is called by a task to cease to exist as a task. It is
* called implicitly when the "main" routine of a spawned task is exited.
* The <code> parameter will be stored in the WIND_TCB for
* possible use by the delete hooks, or post-mortem debugging.
*
* ERRNO: N/A
*
* SEE ALSO: taskDelete(),
* .I "American National Standard for Information Systems -"
* .I "Programming Language - C, ANSI X3.159-1989: Input/Output (stdlib.h),"
* .pG "Basic OS"
*/
void exit
(
int code /* code stored in TCB for delete hooks */
)
{
taskIdCurrent->exitCode = code; /* store the exit code */
taskLock (); /* LOCK PREEMPTION */
taskIdCurrent->options |= VX_UNBREAKABLE; /* mark as unbreakable */
if (taskBpHook != NULL) /* call the debugger hook */
(* taskBpHook) (taskIdCurrent); /* to remove all breakpoints */
taskUnlock (); /* UNLOCK PREEMPTION */
taskDestroy (0, TRUE, WAIT_FOREVER, FALSE); /* self destruct */
}
assert运用
对于断言,相信大家都不陌生,大多数编程语言也都有断言这一特性。简单地讲,断言就是对某种假设条件进行检查。在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数,其原型定义在
默认情况下,assert 宏只有在 Debug 版本(内部调试版本)中才能够起作用,而在 Release 版本(发行版本)中将被忽略。当然,也可以通过定义宏或设置编译器参数等形式来在任何时候启用或者禁用断言检查(不建议这么做)。同样,在程序投入运行后,最终用户在遇到问题时也可以重新起用断言。这样可以快速发现并定位软件问题,同时对系统错误进行自动报警。对于在系统中隐藏很深,用其他手段极难发现的问题也可以通过断言进行定位,从而缩短软件问题定位时间,提高系统的可测性。
不管断言宏最终是用什么样的方式进行定义,其所定义宏的主要目的都是要使用它来对传递给相应函数的参数进行确认检查。如果违背了这条宏定义原则,那么所定义的宏将会偏离方向,失去宏定义本身的意义。与此同时,为不影响标准 assert 宏的使用,最好使用其他的名字。
/*使用断言测试*/
#ifdef DEBUG
/*处理函数原型*/
void Assert(char * filename, unsigned int lineno);
#define ASSERT(condition)\
if(condition)\
NULL; \
else\
Assert(__FILE__ , __LINE__)
/*不使用断言测试*/
#else
#define ASSERT(condition) NULL
#endif
void Assert(char * filename, unsigned int lineno)
{
fflush(stdout);
fprintf(stderr,"\nAssert failed: %s, line %u\n",filename, lineno);
fflush(stderr);
abort();
}
如果定义了 DEBUG,ASSERT 将被扩展为一个if语句,否则执行“#define ASSERT(condition) NULL”替换成 NULL。
这里需要注意的是,因为在编写 C 语言代码时,在每个语句后面加一个分号“;”已经成为一种约定俗成的习惯,因此很有可能会在“Assert(FILE,LINE)”调用语句之后习惯性地加上一个分号。实际上并不需要这个分号,因为用户在调用 ASSERT 宏时,已经给出了一个分号。面对这种问题,我们可以使用“do{}while(0)”结构进行处理
#define ASSERT(condition)\
do{ \
if(condition)\
NULL; \
else\
Assert(__FILE__ , __LINE__);\
}while(0)
现在,将不再为分号“;”而担心了,调用示例如下:
void Test(unsigned char *str)
{
ASSERT(str != NULL);
/*函数处理代码*/
}
int main(void)
{
Test(NULL);
return 0;
}
对标准的 assert 宏来说,自定义的 ASSERT 宏将具有更大的灵活性,可以根据自己的需要打印输出不同的信息,同时也可以对不同类型的错误或者警告信息使用不同的断言,这也是在工程代码中经常使用的做法。当然,如果没有什么特殊需求,还是建议使用标准 assert 宏。
尽量在函数中使用断言来检查参数的合法性
在函数中使用断言来检查参数的合法性是断言最主要的应用场景之一,它主要体现在如下 3 个方面:
在代码执行之前或者在函数的入口处,使用断言来检查参数的合法性,这称为前置条件断言。
在代码执行之后或者在函数的出口处,使用断言来检查参数是否被正确地执行,这称为后置条件断言。
在代码执行前后或者在函数的入出口处,使用断言来检查参数是否发生了变化,这称为前后不变断言。
例如,在上面的 Memcpy 函数中,除了可以通过“assert(dest !=NULL&&src!=NULL);”语句在函数的入口处检查 dest 与 src 参数是否传入 NULL 指针之外,还可以通过“assert(tmp_dest>=tmp_src+len||tmp_src>=tmp_dest+len);”语句检查两个内存块是否发生重叠。
建议每一个 assert 宏只检验一个条件,这样做的好处就是当断言失败时,便于程序排错。试想一下,如果在一个断言中同时检验多个条件,当断言失败时,我们将很难直观地判断哪个条件失败
避免使用断言去检查程序错误
在对断言的使用中,一定要遵循这样一条规定:对来自系统内部的可靠的数据使用断言,对于外部不可靠数据不能够使用断言,而应该使用错误处理代码。换句话说,断言是用来处理不应该发生的非法情况,而对于可能会发生且必须处理的情况应该使用错误处理代码,而不是断言。
在通常情况下,系统外部的数据(如不合法的用户输入)都是不可靠的,需要做严格的检查(如某模块在收到其他模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现)才能放行到系统内部,这相当于一个守卫。而对于系统内部的交互(如子程序调用),如果每次都去处理输入的数据,也就相当于系统没有可信的边界,这样会让代码变得臃肿复杂。事实上,在系统内部,传递给子程序预期的恰当数据应该是调用者的责任,系统内的调用者应该确保传递给子程序的数据是恰当且可以正常工作的。这样一来,就隔离了不可靠的外部环境和可靠的系统内部环境,降低复杂度。
但是在代码编写与测试阶段,代码很可能包含一些意想不到的缺陷,也许是处理外部数据的程序考虑得不够周全,也许是调用系统内部子程序的代码存在错误,造成子程序调用失败。这个时候,断言就可以发挥作用,用来确诊到底是哪部分出现了问题而导致子程序调用失败。在清理所有缺陷之后,就建立了内外有别的信用体系。等到发行版的时候,这些断言就没有存在的必要了。因此,不能用断言来检查最终产品肯定会出现且必须处理的错误情况。
总之记住一句话:断言是用来检查非法情况的,而不是测试和处理错误的。因此,不要混淆非法情况与错误情况之间的区别,后者是必然存在且一定要处理的。
尽量在防错性程序设计中使用断言来进行错误报警
对于防错性程序设计,相信有经验的程序员并不陌生,大多数教科书也都鼓励程序员进行防错性程序设计。在程序设计过程中,总会或多或少产生一些错误,这些错误有些属于设计阶段隐藏下来的,有些则是在编码中产生的。为了避免和纠正这些错误,可在编码过程中有意识地在程序中加进一些错误检查的措施,这就是防错性程序设计的基本思想。其中,它又可以分为主动式防错程序设计和被动式防错程序设计两种。
主动式防错程序设计是指周期性地对整个程序或数据库进行搜查或在空闲时搜查异常情况。它既可以在处理输入信息期间使用,也可以在系统空闲时间或等待下一个输入时使用。如下面所列出的检查均适合主动式防错程序设计。
内存检查:如果在内存的某些块中存放了一些具有某种类型和范围的数据,则可对它们做经常性检查。
标志检查:如果系统的状态是由某些标志指示的,可对这些标志做单独检查。
反向检查:对于一些从一种代码翻译成另一种代码或从一种系统翻译成另一种系统的数据或变量值,可以采用反向检查,即利用反向翻译来检查原始值的翻译是否正确。
状态检查:对于某些具有多个操作状态的复杂系统,若用某些特定的存储值来表示这些状态,则可通过单独检查存储值来验证系统的操作状态。
连接检查:当使用链表结构时,可检查链表的连接情况。
时间检查:如果已知道完成某项计算所需的最长时间,则可用定时器来监视这个时间。
其他检查:程序设计人员可经常仔细地对所使用的数据结构、操作序列和定时以及程序的功能加以考虑,从中得到要进行哪些检查的启发。
被动式防错程序设计则是指必须等到某个输入之后才能进行检查,也就是达到检查点时才能对程序的某些部分进行检查。一般所要进行的检查项目如下:
来自外部设备的输入数据,包括范围、属性是否正确。
由其他程序所提供的数据是否正确。
数据库中的数据,包括数组、文件、结构、记录是否正确。
操作员的输入,包括输入的性质、顺序是否正确。
栈的深度是否正确。
数组界限是否正确。
表达式中是否出现零分母情况。
正在运行的程序版本是否是所期望的(包括最后系统重新组合的日期)。
通过其他程序或外部设备的输出数据是否正确。
虽然防错性程序设计被誉为有较好的编码风格,一直被业界强烈推荐。但防错性程序设计也是一把双刃剑,从调试错误的角度来看,它把原来简单的、显而易见的缺陷转变成晦涩的、难以检测的缺陷,而且诊断起来非常困难。从某种意义上讲,防错性程序设计隐瞒了程序的潜在错误。
当然,对于软件产品,希望它越健壮越好。但是调试脆弱的程序更容易帮助我们发现其问题,因为当缺陷出现的时候它就会立即表现出来。因此,在进行防错性程序设计时,如果“不可能发生”的事情的确发生了,则需要使用断言进行报警,这样,才便于程序员在内部调试阶段及时对程序问题进行处理,从而保证发布的软件产品具有良好的健壮性。
“断言是在程序某个特定点的一个布尔表达式,除非程序中有缺陷(Bug),否则它的值将为真。”
想要理解上述断言定义的开发人员应该留意下面三个要点:
·断言会评估一个表达式是真还是假
·断言是在代码中的某个点对系统状态的一种假设
·断言会验证系统假设,如果不为真,就表明代码中有一个缺陷
开发人员应该切记:断言是用于检测缺陷的,不能用于错误处理。错误处理是设计用于响应错误的用户输入和意外的事件顺序的软件。错误在系统中预料是会发生的,但仅仅是因为有无效的输入而并不意味着代码中有缺陷。错误处理应该与缺陷寻找分开来
————————————————
版权声明:本文为CSDN博主「小熊coder」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41854911/article/details/119453790
keil c51 assert
#ifndef __ASSERT_H__
#define __ASSERT_H__
#undef assert
#ifndef __ASSERT_INC
#include <stdio.h> /* prototype for 'printf' */
#define __ASSERT_INC
#endif
#ifndef NDEBUG
#define assert(expr) \
if (expr) { ; } \
else {\
printf("Assert failed: " #expr " (file %s line %d)\n", __FILE__, (int) __LINE__ );\
while (1);\
}
#else
#define assert(expr)
#endif
#endif
1) 如果条件出了问题可以把I/O的值改变,以标识说是状态也好,特征也行,反正就这么个东西.同样您可以用万用表来测一下各I/O口的值,以至可以在执行的时候找到出错的地方.同时禁止MCU去响应任何的中断请求,以防在中断里会改变I/O的值.
2) 如果条件出了问题我的机器就没必要继续工作下去了,就停在出问题的地方.如果这个时候是通过软件仿真就能直接看到PC停在什么地方,使哥哥们可以直接找到问题所在.(后来偶一看Keil C51也采用和偶一样的做法,这是个巧合了).
3) 哥哥们可以任选I/O口对其的任意位进行设置.
1. #ifndef NODEBUG
2. #define true 1
3. /*为循环提供一个宏*/
4. #ifndef _IN_LASSERT_H_
5. #include <reg51.h> /*用到标准51里的,EA中断使能位*/
6. #define _IN_LASSERT_H_
7. #endif
8. /*
9. 宏名:lassert(Condition,SetPort,SetBit)
10. Condition:为传入条件
11. SetPort:若出错,产生反应的端口
12. SetBit: 若出错,产生反应的端口及对应位,SetBit <= 7;51的每个I/O只有八位.照顾一下.
13. 功能:此为从写标准库的宏,专为适应51的特点.
14. */
15. #define lassert(Condition,SetPort,SetBit) \
16. { \
if (!Condition) \
{ \
EA = 0; \
SetPort &= (0x01 << SetBit); \
while (true); \
} \
17. }
18. #else
19. /*将宏屏蔽,我们聪明的编译器会优化掉它可能产生的代码.*/
20. #define lassert(Condition,SetPort,SetBit)\
21. { \
; \
22. }
23. #endif
24. #endif
注:16句内的:EA = 1;允许MCU去响应中断, EA = 0;MCU不响应任何中断;
后记:
一、 现在这种端口置位的方法太简单了,一个I/O只能有八种状态,只能在小型的程序,或小范围内应用.除了用置位的方法之外,还可以用端口输出值的办法这样一个端口就能输出256种状态.这样将大大丰富了表求的状态,不过也太来了读状态的问题,可以自已制一个显示电路,用三个译码器(或一个MCU)读端口的输入,通过三个LED二极管.显示出相应的数值来.(不知道传说中的PCDEBUG卡用的是不是差不多的原理,应该更加复杂吧).
KEIL C51头文件
标准C语言头文件ISO
C标准定义的头文件(24项)
<absacc.h>”即可使用其中定义的宏来访问绝对地址,包括:CBYTE、XBYTE、PWORD、DBYTE、CWORD、XWORD、PBYTE、DWORD
<assert.h>验证程序断言
<complex.h>支持复数算术运算
<ctype.h>字符类型
<errno.h>出错码
<fenv.h>浮点环境
<float.h>浮点常量
<inttypes.h>整型格式转换
<iso646.h>替代关系操作符宏
<limits.h>实现常量
<locale.h>局部类别
<math.h>数学常量
<setjmp.h>非局部
goto<signal.h>信号
<stdarg.h>可变参数表
<stdbool.h>布尔类型和值
<stddef.h>标准定义
<stdint.h>整型
<stdio.h>标准
I/O库<stdlib.h>实用程序库函数
<string.h>字符串操作
<tgmath.h>通用类型数学宏
<time.h>时间和日期
<wchar.h>宽字符支持
<wctype.h>宽字符分类和映射支持
POSIX标准定义的必须的头文件(26项)
<dirent.h>目录项
<fcntl.h>文件控制
<fnmatch.h>文件名匹配类型
<glob.h>路径名模式匹配类型
<grp.h>组文件
<netdb.h>网络数据库操作
<pwd.h>口令文件
<regex.h>正则表达式
<tar.h>tar归档值
<termios.h>终端
I/O<unistd.h>符号常量
<utime.h>文件时间
<wordexp.h>字扩展类型
<arpa/inet.h>Internet定义
<net/if.h>套接字本地接口
<netinet/in.h>Internet地址族
<netinet/tcp.h>传输控制协议
<sys/mman.h>内存管理声明
<sys/select.h>select函数
<sys/socket.h>套接字接口
<sys/stat.h>文件状态
<sys/times.h>进程时间
<sys/types.h>基本系统数据类型
<sys/un.h>UNIX域套接字定义
<sys/utsname.h>系统名
<sys/wait.h>进程控制
POSIX标准定义的XSI扩展头文件(26项)
<cpio.h>cpio归档值
<dlfcn.h>动态链接
<fmtmsg.h>消息显示结构
<ftw.h>文件树漫游
<iconv.h>代码集转换实用程序
<langinfo.h>语言信息常量
<libgen.h>模式匹配函数定义
<monetary.h>货币类型
<ndbm.h>数据库操作
<nl_types.h>消息类别
<poll.h>轮询函数
<search.h>搜索表
<strings.h>字符串操作
<syslog.h>系统出错日志记录
<ucontext.h>用户上下文
<ulimit.h>用户限制
<utmpx.h>用户帐户数据库
<sys/ipc.h>IPC
<sys/msg.h>消息队列
<sys/resource.h>资源操作
<sys/sem.h>信号量
<sys/shm.h>共享存储
<sys/statvfs.h>文件系统信息
<sys/time.h>时间类型
<sys/timeb.h>附加的时间
<sys/uio.h>矢量
I/O操作POSIX标准定义的可选头文件(8项)
<aio.h>异步I/O
<mqueue.h>消息队列
<pthread.h>线程
<sched.h>执行调度
<semaphore.h>信号量
<spawn.h>实时spawn接口
<stropts.h>XSISTREAMS接口
<trace.h>时间跟踪