控制函数用于对C语言程序的标准控制流(如if/else、switch、for等)提供扩展,在头文件assert.h、setjmp.h和signal.h中提供,分别提供表达式断言功能、非本地跳转功能、信号处理功能。
1、assert.h: 提供用于断言的assert宏。程序中若没有定义NDEBUG,则asset(exp)对表达式exp进行断言,若断言为假(即为0),则会调用__assert_fail函数打印一条“断言失败”的消息,并终止程序。若定义了NDEBUG宏,则assert通常为空语句。
/* ISO C99 Standard: 7.2 诊断 <assert.h> */
#ifdef _ASSERT_H
# undef _ASSERT_H
# undef assert
# undef __ASSERT_VOID_CAST
# ifdef __USE_GNU
# undef assert_perror
# endif
#endif /* assert.h */
#define _ASSERT_H 1
#include <features.h>
#if defined __cplusplus && __GNUC_PREREQ (2,95)
# define __ASSERT_VOID_CAST static_cast<void>
#else
# define __ASSERT_VOID_CAST (void)
#endif
/* void assert (int expression);
如果定义了NDEBUG,什么也不做,如果没有定义,且EXPRESSION为0,则打印一个错误消息,然后终止程序
*/
#ifdef NDEBUG
# define assert(expr) (__ASSERT_VOID_CAST (0))
/* void assert_perror (int errnum);
如果定义了NDEBUG,什么也不做。如果没有定义,且ERRNUM为0,则打印对应于错误码ERRNUM的错误消息,
然后终止程序 */
# ifdef __USE_GNU
# define assert_perror(errnum) (__ASSERT_VOID_CAST (0))
# endif
#else /* 没有定义NDEBUG */
#ifndef _ASSERT_H_DECLS
#define _ASSERT_H_DECLS
__BEGIN_DECLS
/* 这里打印一条“断言失败”的消息,然后终止程序 */
extern void __assert_fail (__const char *__assertion, __const char *__file,
unsigned int __line, __const char *__function)
__THROW __attribute__ ((__noreturn__));
/* 与上面一样,但打印错误码ERRNUM的错误消息 */
extern void __assert_perror_fail (int __errnum, __const char *__file,
unsigned int __line,
__const char *__function)
__THROW __attribute__ ((__noreturn__));
/* 下面的函数在这里并没有使用,但为了与标准兼容,还是需要提供 */
extern void __assert (const char *__assertion, const char *__file, int __line)
__THROW __attribute__ ((__noreturn__));
__END_DECLS
#endif /* Not _ASSERT_H_DECLS */
/* 使用上面的函数来定义完整的assert宏 */
# define assert(expr) /
((expr) /
? __ASSERT_VOID_CAST (0) /
: __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))
# ifdef __USE_GNU
# define assert_perror(errnum) /
(!(errnum) /
? __ASSERT_VOID_CAST (0) /
: __assert_perror_fail ((errnum), __FILE__, __LINE__, __ASSERT_FUNCTION))
# endif
/* 2.4及之后版本的GCC定义了一个魔术变量__PRETTY_FUNCTION__,它包含当前定义的函数名,这在2.6版本之前
的G++中打破了。C9x有一个类似的名为__func__的变量,但首选的是GCC的这个变量,因为它重新覆盖了C++中的
这个函数名 */
# if defined __cplusplus ? __GNUC_PREREQ (2, 6) : __GNUC_PREREQ (2, 4)
# define __ASSERT_FUNCTION __PRETTY_FUNCTION__
# else
# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
# define __ASSERT_FUNCTION __func__
# else
# define __ASSERT_FUNCTION ((__const char *) 0)
# endif
# endif
#endif /* NDEBUG. */
/* assert.c:assert功能的实现,核心的函数为__assert_fail */
#include <assert.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysdep.h>
#include <unistd.h>
extern const char *__progname;
#ifdef USE_IN_LIBIO
# include <wchar.h>
# include <libio/iolibio.h>
# define fflush(s) INTUSE(_IO_fflush) (s)
#endif
/* 本函数当传递一个包含断言表达式的字符串、一个文件名、和一个行号时,在标准
错误流上用以下格式打印一条消息:
a.c:10: foobar: Assertion `a == b' failed.
然后通过调用abort来终止程序的执行 */
#ifdef FATAL_PREPARE_INCLUDE
# include FATAL_PREPARE_INCLUDE
#endif
#undef __assert_fail
void
__assert_fail (const char *assertion, const char *file, unsigned int line,
const char *function)
{
char *buf;
#ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif
/* 把指定格式的消息写入到buf中 */
if (__asprintf (&buf, _("%s%s%s:%u: %s%sAssertion `%s' failed./n"),
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion) >= 0)
{
(void) __fxprintf (NULL, "%s", buf); /* 打印buf中的消息 */
(void) fflush (stderr); /* 刷新标准错误流的状态(注意stderr并不缓冲) */
/* 我们要释放消息缓冲区,因为应用程序可能会捕捉到SIGABRT信号 */
free (buf);
}
else /* 消息写入未成功 */
{
/* 至少要打印一个最小的消息 */
static const char errstr[] = "Unexpected error./n";
__libc_write (STDERR_FILENO, errstr, sizeof (errstr) - 1);
}
abort (); /* 调用abort终止程序 */
}
hidden_def(__assert_fail)
/* assert-perr.c:assert_perror功能是GNU的一个扩展,核心的函数是__assert_perror_fail */
#include <assert.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysdep.h>
#include <unistd.h>
extern const char *__progname;
#ifdef USE_IN_LIBIO
# include <wchar.h>
# include <libio/iolibio.h>
# define fflush(s) INTUSE(_IO_fflush) (s)
#endif
/* 本函数当传递一个包含断言表达式的字符串、一个文件名、和一个行号时,在标准
错误流上用以下格式打印一条消息:
a.c:10: foobar: Unexpected error: Computer bought the farm
然后通过调用abort来终止程序的执行 */
#ifdef FATAL_PREPARE_INCLUDE
# include FATAL_PREPARE_INCLUDE
#endif
void
__assert_perror_fail (int errnum,
const char *file, unsigned int line,
const char *function)
{
char errbuf[1024];
char *buf;
#ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif
/* 把指定格式的消息写入到buf中 */
if (__asprintf (&buf, _("%s%s%s:%u: %s%sUnexpected error: %s./n"),
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
__strerror_r (errnum, errbuf, sizeof errbuf)) >= 0)
{
(void) __fxprintf (NULL, "%s", buf); /* 打印buf中的消息 */
(void) fflush (stderr); /* 刷新标准错误流的状态(注意stderr并不缓冲) */
/* 我们要释放消息缓冲区,因为应用程序可能会捕捉到SIGABRT信号 */
free (buf);
}
else /* 消息写入未成功 */
{
/* 至少要打印一个最小的消息 */
static const char errstr[] = "Unexpected error./n";
__libc_write (STDERR_FILENO, errstr, sizeof (errstr) - 1);
}
abort (); /* 调用abort终止程序 */
}
libc_hidden_def (__assert_perror_fail)
/* __assert.c:__assert函数的实现 */
/* 我们必须参看原型:没有定义NDEBUG时才会在<assert.h>有__assert的原型 */
#undef NDEBUG
#include <assert.h>
/* 提供__assert函数是为了与标准兼容,它的功能由__assert_fail取代 */
void
__assert (const char *assertion, const char *file, int line)
{
__assert_fail (assertion, file, line, (const char *) 0);
}
解释:
(1)GNU还提供了一个扩展assert_perror,功能与assert类似,只不过打印指定的错误码对应的消息,通过__assert_perror_fail函数来实现。另外提供的_assert函数是为了与标准兼容,它的功能由__assert_fail取代。
(2)__assert_fail和__assert_perror_fail函数都是把指定格式的消息写入到buf中,然后打印buf中的消息,并刷新标准错误流的状态,释放消息缓冲区buf,最后要调用abort函数终止程序。
2、setjmp.h: 标准库函数setjmp和longjmp实现基本形式的非本地跳转。setjmp用于保存调用者的堆栈环境(被保存在表示跳转缓冲区的jmp_buf型数组ENV中),然后返回0。longjmp用于跳转到保存堆栈环境的地方,并从那里的setjmp调用返回,返回指定的状态码,如果状态码为0则返回1。
/* ISO C99 Standard: 7.13 非本地跳转 <setjmp.h> */
#ifndef _SETJMP_H
#define _SETJMP_H 1
#include <features.h>
__BEGIN_DECLS
#include <bits/setjmp.h> /* 获取__jmp_buf */
#include <bits/sigset.h> /* 获取__sigset_t */
/* 调用环境,可能还会附加一个被保存的信号掩码 */
struct __jmp_buf_tag
{
/* 注意:依赖于机器的__sigsetjmp定义假定一个jmp_buf从__jmp_buf开始,后面紧跟着
__mask_was_saved。不要移动这些成员或者在前面添加其他的成员 */
__jmp_buf __jmpbuf; /* 调用环境 */
int __mask_was_saved; /* 保存信号掩码吗? */
__sigset_t __saved_mask; /* 被保存的信号掩码 */
};
__BEGIN_NAMESPACE_STD
typedef struct __jmp_buf_tag jmp_buf[1]; /* 定义jmp_buf数组 */
/* 在ENV中保存调用环境,同时也会保存信号掩码,返回0 */
extern int setjmp (jmp_buf __env) __THROW;
__END_NAMESPACE_STD
/* 在ENV中保存调用环境,如果SAVEMASK不为零,则同时也会保存信号掩码。返回0。
这是sigsetjmp函数的内部名称 */
extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROW;
#ifndef __FAVOR_BSD
/* 在ENV中保存调用环境,不保存信号掩码。返回0 */
extern int _setjmp (struct __jmp_buf_tag __env[1]) __THROW;
/* 不保存信号掩码。这与BSD函数'_setjmp'等价 */
# define setjmp(env) _setjmp (env)
#else
/* 在4.3 BSD兼容模式下,setjmp像sigsetjmp(ENV,1)一样来保存信号掩码。我们
必须定义一个宏,因为ISO C指出setjmp是完整的 */
# define setjmp(env) setjmp (env)
#endif /* Favor BSD. */
__BEGIN_NAMESPACE_STD
/* 跳转到保存在ENV中的环境处,使那里的setjmp调用返回VAL,或者返回1(如果VAL为0) */
extern void longjmp (struct __jmp_buf_tag __env[1], int __val)
__THROW __attribute__ ((__noreturn__));
__END_NAMESPACE_STD
#if defined __USE_BSD || defined __USE_XOPEN
/* 与上面相同。通常_longjmp与setjmp一起使用,不保存信号掩码。但怎样保存ENV决定了longjmp
是否恢复掩码;而_longjmp只是一个别名 */
extern void _longjmp (struct __jmp_buf_tag __env[1], int __val)
__THROW __attribute__ ((__noreturn__));
#endif
#ifdef __USE_POSIX
/* 对jmp_buf和sigjmp_buf使用同样的类型。__mask_was_saved标志决定
longjmp是否将恢复信号掩码 */
typedef struct __jmp_buf_tag sigjmp_buf[1];
/* 在ENV中保存调用环境,同时也会保存信号掩码(如果SAVEMASK不为零),返回0 */
# define sigsetjmp(env, savemask) __sigsetjmp (env, savemask)
/* 跳转到保存在ENV中的环境处,使那里的sigsetjmp调用返回VAL,或者返回(如果VAL为0)
恢复信号掩码(如果那个sigsetjmp保存了它的话)。本函数只是longjmp的一个别名 */
extern void siglongjmp (sigjmp_buf __env, int __val)
__THROW __attribute__ ((__noreturn__));
#endif /* Use POSIX. */
__END_DECLS
#endif /* setjmp.h */
/* setjmp.c:setjmp函数的实现 */
#include <errno.h>
#include <setjmp.h>
/* 在ENV中人嘎当前程序的位置,然后返回0 */
int
__libc_sigsetjmp (jmp_buf env, int savemask)
{
/* 如果被请求的话,保存信号掩码 */
__sigjmp_save (env, savemask);
__set_errno (ENOSYS);
/* 没有信号失败 */
return 0;
}
weak_alias (__libc_sigsetjmp, __sigsetjmp)
stub_warning (__sigsetjmp)
#include <stub-tag.h>
/* longjmp.c:longjmp函数的实现 */
#include <stddef.h>
#include <setjmp.h>
#include <signal.h>
/* 在ENV中的相应成员上设置信号掩码,并且跳转到ENV指定的位置处,导致那里的
setjmp调用返回VAL,或者返回1(如果VAL为0) */
void
__libc_siglongjmp (sigjmp_buf env, int val)
{
/* 在展开的堆栈结构上需要做清理工作 */
_longjmp_unwind (env, val);
if (env[0].__mask_was_saved)
/* 恢复被保存的掩码 */
(void) __sigprocmask (SIG_SETMASK, &env[0].__saved_mask,
(sigset_t *) NULL);
/* 调用依赖于机器的函数来恢复机器状态 */
__longjmp (env[0].__jmpbuf, val ?: 1);
}
strong_alias (__libc_siglongjmp, __libc_longjmp)
libc_hidden_def (__libc_longjmp)
weak_alias (__libc_siglongjmp, _longjmp)
weak_alias (__libc_siglongjmp, longjmp)
weak_alias (__libc_siglongjmp, siglongjmp)
注意setjmp和longjmp函数都是直接调用内部函数来完成工作的。头文件setjmp.h中的其他部分都是BSD、XOPEN或POSIX方面的扩展。
3、signal.h: 定义了信号原子类型sig_atomic_t、信号处理函数的注册signal、发送信号的函数raise。它包含了bits/signum.h,这个Linux系统的头文件中定义了C标准中的信号及其他一些信号。标准C语言中的信号有SIGINT,SIGILL,SIGABRT,SIGFPE,SIGSEGV,SIGTERM,还要3个特殊的信号SIGERR,SIG_DFL, SIG_IGN,因此标准C语言中总共有9个信号。
signal.h代码如下:
/* ISO C99 Standard: 7.14 信号处理 <signal.h> */
#ifndef _SIGNAL_H
#if !defined __need_sig_atomic_t && !defined __need_sigset_t
# define _SIGNAL_H
#endif
#include <features.h> /* 定义了一些编译选项 */
__BEGIN_DECLS
#include <bits/sigset.h> /* 获取__sigset_t和__sig_atomic_t类型 */
/* 一个可以自动修改的内部类型,它不可能是一个到达运算中间的信号 */
#if defined __need_sig_atomic_t || defined _SIGNAL_H
# ifndef __sig_atomic_t_defined
# define __sig_atomic_t_defined
__BEGIN_NAMESPACE_STD
typedef __sig_atomic_t sig_atomic_t; /* 定义信号原子类型 */
__END_NAMESPACE_STD
# endif
# undef __need_sig_atomic_t
#endif
#if defined __need_sigset_t || (defined _SIGNAL_H && defined __USE_POSIX)
# ifndef __sigset_t_defined
# define __sigset_t_defined
typedef __sigset_t sigset_t; /* 信号集类型 */
# endif
# undef __need_sigset_t
#endif
#ifdef _SIGNAL_H
#include <bits/types.h> /* 定义了一些扩展整数类型 */
#include <bits/signum.h> /* 定义了C标准中的信号及其他一些信号 */
#if defined __USE_XOPEN || defined __USE_XOPEN2K
# ifndef __pid_t_defined
typedef __pid_t pid_t; /* 进程ID类型 */
# define __pid_t_defined
#endif
#ifdef __USE_XOPEN
# endif
# ifndef __uid_t_defined
typedef __uid_t uid_t; /* 用户ID类型 */
# define __uid_t_defined
# endif
#endif /* Unix98 */
/* 信号处理函数类型 */
typedef void (*__sighandler_t) (int);
/* X/Open的signal定义指定SVID的语义。当需要X/Open兼容性时使用另外一个函数sysv_signal */
extern __sighandler_t __sysv_signal (int __sig, __sighandler_t __handler)
__THROW;
#ifdef __USE_GNU
extern __sighandler_t sysv_signal (int __sig, __sighandler_t __handler)
__THROW;
#endif
/* 为信号SIG设置信号处理函数HANDLER,返回原来的信号处理函数,出错则返回SIG_ERR
默认情况下signal拥有BSD风格的语义 */
__BEGIN_NAMESPACE_STD
#ifdef __USE_BSD
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
__THROW;
#else
/* 确保使用的signal实现是SVID的版本 */
# ifdef __REDIRECT_NTH
extern __sighandler_t __REDIRECT_NTH (signal,
(int __sig, __sighandler_t __handler),
__sysv_signal);
# else
# define signal __sysv_signal
# endif
#endif
__END_NAMESPACE_STD
#ifdef __USE_XOPEN
/* X/Open的signal定义与BSD的版本有冲突,因此它们定义定义另外一个函数bsd_singnal */
extern __sighandler_t bsd_signal (int __sig, __sighandler_t __handler)
__THROW;
#endif
/* 发送信号SIG给PID表示的这个进程。如果PID为0,则发送信号SIG给当前进程组的所有进程。
如果PID<-1,则发送SIG给PID表示的进程组中的所有进程 */
#ifdef __USE_POSIX
extern int kill (__pid_t __pid, int __sig) __THROW;
#endif /* Use POSIX. */
#if defined __USE_BSD || defined __USE_XOPEN_EXTENDED
/* 发送信号SIG给进程组PGRP中的所有进程。如果PGRP为0,则发送信号SIG给
当前进程组的所有进程 */
extern int killpg (__pid_t __pgrp, int __sig) __THROW;
#endif /* Use BSD || X/Open Unix. */
__BEGIN_NAMESPACE_STD
/* 发送信号SIG。例如,发送SIG给你自己 */
extern int raise (int __sig) __THROW;
__END_NAMESPACE_STD
/* 下面都是一些扩展或内部函数 */
#endif /* signal.h */
__END_DECLS
#endif /* not signal.h */
bits/signum.h代码如下:
/* bits/signum.h:信号码定义,Linux版本 */
#ifdef _SIGNAL_H
/* 伪信号函数 */
#define SIG_ERR ((__sighandler_t) -1) /* 出错时返回 */
#define SIG_DFL ((__sighandler_t) 0) /* 默认行为 */
#define SIG_IGN ((__sighandler_t) 1) /* 忽略信号 */
#ifdef __USE_UNIX98
# define SIG_HOLD ((__sighandler_t) 2) /* 给信号加保留的掩码 */
#endif
/* 信号 */
#define SIGHUP 1 /* 挂断 (POSIX). */
#define SIGINT 2 /* 中断 (ANSI). */
#define SIGQUIT 3 /* 退出 (POSIX). */
#define SIGILL 4 /* 非法指令 (ANSI). */
#define SIGTRAP 5 /* 跟踪捕捉 (POSIX). */
#define SIGABRT 6 /* 异常终止,如调用abort (ANSI). */
#define SIGIOT 6 /* 图像输出终端(IOT)陷阱 (4.2 BSD). */
#define SIGBUS 7 /* 总线错误 (4.2 BSD). */
#define SIGFPE 8 /* 浮点数异常 (ANSI). */
#define SIGKILL 9 /* 杀死,此信号可解锁 (POSIX). */
#define SIGUSR1 10 /* 用户定义信号1 (POSIX). */
#define SIGSEGV 11 /* 段错误,即无效内存访问 (ANSI). */
#define SIGUSR2 12 /* 用户定义信号2 (POSIX). */
#define SIGPIPE 13 /* 管道错误 (POSIX). */
#define SIGALRM 14 /* 时钟警告 (POSIX). */
#define SIGTERM 15 /* 终止 (ANSI). */
#define SIGSTKFLT 16 /* 堆栈故障 */
#define SIGCLD SIGCHLD /* 等同于SIGCHLD (System V). */
#define SIGCHLD 17 /* 子进程状态已经改变 (POSIX). */
#define SIGCONT 18 /* 继续 (POSIX). */
#define SIGSTOP 19 /* 停止,此信号可解锁 (POSIX). */
#define SIGTSTP 20 /* 键盘发的停止信号 (POSIX). */
#define SIGTTIN 21 /* 从tty进行后台读操作 (POSIX). */
#define SIGTTOU 22 /* 后台写入tty (POSIX). */
#define SIGURG 23 /* socket紧急状态 (4.2 BSD). */
#define SIGXCPU 24 /* 超出CPU限制 (4.2 BSD). */
#define SIGXFSZ 25 /* 超出文件大小限制 (4.2 BSD). */
#define SIGVTALRM 26 /* 时钟虚拟警告 (4.2 BSD). */
#define SIGPROF 27 /* 时钟Profiling警告 (4.2 BSD). */
#define SIGWINCH 28 /* 窗口大小改变 (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* 可移植的事件发生 (System V). */
#define SIGIO 29 /* 可以执行I/O (4.2 BSD). */
#define SIGPWR 30 /* 重启时电源失效 (System V). */
#define SIGSYS 31 /* 错误的系统调用 */
#define SIGUNUSED 31
#define _NSIG 65 /* 最大的信号值+1(包括实时信号) */
#define SIGRTMIN (__libc_current_sigrtmin ())
#define SIGRTMAX (__libc_current_sigrtmax ())
/* 这些是内核的硬限制。这些值不应该在用户级别使用 */
#define __SIGRTMIN 32
#define __SIGRTMAX (_NSIG - 1)
#endif /* <signal.h> included. */
signal函数和raise函数的实现:
/* signal.c:signal函数的实现 */
#include <errno.h>
#include <signal.h>
/* 为信号SIG设置信号处理函数HANDLER,返回原来的信号处理函数,出错则返回SIG_ERR */
__sighandler_t
signal (sig, handler)
int sig;
__sighandler_t handler;
{
__set_errno (ENOSYS);
return SIG_ERR;
}
weak_alias (signal, ssignal)
stub_warning (signal)
stub_warning (ssignal)
#include <stub-tag.h>
/* raise.c:raise函数的实现 */
#include <signal.h>
#include <errno.h>
/* 发送SIG */
int
raise (sig)
int sig;
{
__set_errno (ENOSYS);
return -1;
}
weak_alias (raise, gsignal)
stub_warning (raise)
stub_warning (gsignal)
#include <stub-tag.h>
注意signal和raise函数并没有做任何特别的实现,只是处理了一下出错时的情况(返回SIG_ERR,即-1)。真正的实现使用Linux中有对应功能的函数,signal直接映射到Linux的ssignal函数,raise直接映射到Linux的gsignal函数。