C标准库之errno

<errno.h>定义了整数变量extern int errnoerrno是一个由POSIXISO C标准定义的符号,由系统调用和某些库函数根据事件来设置它,用以表明哪里有问题。这个值只有当调用的返回表明错误的时候有用(比如,对于大多数的系统调用是-1,对于大多数的库函数来说是-1或NULL),正确的函数也可以修改errno

Linux中系统调用的错误都存储于 errno 中,errno 由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。

  • 在程序启动时,errno 设置为零,系统调用或 C 标准库中的特定函数修改它的值为一些非零值以表示某些类型的错误。有效的错误number都是非零的,系统调用和库函数不会把errno设为0。 对于某些系统调用和库函数(比如,getpriority(2)),没有错误的时候也会返回-1。在这种情况下,可以在调用之前先将errno设为0,当不确定有没有错误的时候,可以通过查看errno是不是一个非零值来确定是否发生错误。
  • errno的值不会被任何程序清除,因此在使用errno的值之前,先要通过函数(系统调用/库函数)的返回值来确定有错误发生。
  • ISO C标准将errno定义为一个可以修改的int型左值,并且不允许准确声明errno可能是一个宏,也可能被定义成一个变量,这个具体要看编译器自己的实现。
  • 系统调用或库函数正确执行,并不保证errno的值不会被改变。

✏ 1、线程安全性

早些时候,POSIX.1曾把errno定义成extern int errno这种形式,但这种形式实现的errno在多线程环境下是不安全的,errno变量是被多个线程共享的,这样可能线程A发生某些错误改变了errno的值,线程B虽然没有发生任何错误,但是当它检测errno的值的时候,线程B会以为自己发生了错误。

在GCC中,errnothread-local,在一个线程中设置它的值不会影响它在另一个thread中的值。

其定义在bits/errno.h中:

# ifndef __ASSEMBLER__
/* Function to get address of global `errno' variable.  */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
 
#  if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value.  */
#   define errno (*__errno_location ())
#  endif
# endif /* !__ASSEMBLER__ */
#endif /* _ERRNO_H */

error.h中:

/* Declare the `errno' variable, unless it's defined as a macro by
   bits/errno.h.  This is the case in GNU, where it is a per-thread
   variable.  This redeclaration using the macro still works, but it
   will be a function declaration without a prototype and may trigger
   a -Wstrict-prototypes warning.  */
#ifndef errno
extern int errno;
#endif

可以清晰的看到,bits/errno.herrno进行了重定义。从 __attribute__ ((__const__))推测出__errno_location ()会返回与参数无关的与线程绑定的一个特定地址,应用层直接从该地址取出errno的。(关于__attribute__用法可以参考Using GNU C attribute)。但是上面使用了条件编译,也就是有两种方法可以使得gcc重定义errno

  • 不定义宏_LIBC
  • 定义宏_LIBC_REENTRANT

有意思的是,我们在编译时,压根不能设置_LIBC, 在gnu/stubs-64.h中会检测,如果有_LIBC宏定义,直接报错终止预编译:

#ifdef _LIBC
 #error Applications may not define the macro _LIBC
#endif

因此在正常情况下,我们使用gcc编译的程序,全局变量errno一定是线程安全的。

有一个问题,__errno_location是怎么实现的? 在Linux 源代码中查找 __errno_location() 函数,在 errno-loc.c 中有解释:

#include <errno.h>
#include <hurd/threadvar.h>
 
int * __errno_location (void)
{
   
  return (int *) __hurd_threadvar_location (_HURD_THREADVAR_ERRNO);
}
strong_alias (__errno_location, __hurd_errno_location)
libc_hidden_def (__errno_location)

显然,__errno_location() 函数的返回值就是 ****__hurd_threadvar_location(_HURD_THREADVAR_ERRNO) 函数的返回值被强转成 int * 了,那 __hurd_threadvar_location(_HURD_THREADVAR_ERRNO) 又是什么函数呢?

在头文件 <hurd/threadvar.h>中有:

#include <machine-sp.h>		/* Define __thread_stack_pointer.  */
 
/* Return the location of the current thread's value for the
   per-thread variable with index INDEX.  */
 
extern unsigned long int *
__hurd_threadvar_location (enum __hurd_threadvar_index __index) __THROW
     /* This declaration tells the compiler that the value is constant
	given the same argument.  We assume this won't be called twice from
	the same stack frame by different threads.  */
     __attribute__ ((__const__));
 
_HURD_THREADVAR_H_EXTERN_INLINE unsigned long int *
__hurd_threadvar_location (enum __hurd_threadvar_index __index)
{
   
  return __hurd_threadvar_location_from_sp (__index,
					    __thread_stack_pointer ());
}

注:在前面有定义#define _HURD_THREADVAR_H_EXTERN_INLINE extern __inline了解到其实就是 extern __inline ,其中 __inline 表示函数是内联函数。

这是先定义了函数同时在下面直接就给出了函数代码,这个函数的又是调用了__hurd_threadvar_location_from_sp(__index, __thread_stack_pointer ()) 这个函数, 同样在<hurd/threadvar.h> 中往前看,有这样的代码:

extern unsigned long int *__hurd_threadvar_location_from_sp
  (enum __hurd_threadvar_index __index, void *__sp);
_HURD_THREADVAR_H_EXTERN_INLINE unsigned long int *
__hurd_threadvar_location_from_sp (
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值