在errno.h中,这个变量被声明为extern int errno;所以我的问题是,在一些调用后检查errno值或在多线程代码中使用perror()是否安全。这是线程安全的变量吗?如果不是,那么有什么替代方案?
我在x86架构上使用gcc linux。
在errno.h中,这个变量被声明为extern int errno;所以我的问题是,在一些调用后检查errno值或在多线程代码中使用perror()是否安全。这是线程安全的变量吗?如果不是,那么有什么替代方案?
我在x86架构上使用gcc linux。
当前回答
我认为答案是“视情况而定”。线程安全的C运行时库通常将errno实现为函数调用(宏扩展为函数),如果您正在使用正确的标志构建线程代码。
其他回答
在许多Unix系统上,使用-D_REENTRANT编译可以确保errno是线程安全的。
例如:
#if defined(_REENTRANT) || _POSIX_C_SOURCE - 0 >= 199506L
extern int *___errno();
#define errno (*(___errno()))
#else
extern int errno;
/* ANSI C++ requires that errno be a macro */
#if __cplusplus >= 199711L
#define errno errno
#endif
#endif /* defined(_REENTRANT) */
是的,正如errno手册页和其他回复中解释的那样,errno是一个线程局部变量。
然而,有一个很容易被遗忘的愚蠢细节。程序应该在执行系统调用的任何信号处理程序上保存和恢复errno。这是因为信号将由一个进程线程处理,该进程线程可以覆盖其值。
因此,信号处理程序应该保存并恢复errno。喜欢的东西:
void sig_alarm(int signo)
{
int errno_save;
errno_save = errno;
//whatever with a system call
errno = errno_save;
}
我们可以在机器上运行一个简单的程序来检查。
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define NTHREADS 5
void *thread_function(void *);
int
main()
{
pthread_t thread_id[NTHREADS];
int i, j;
for(i=0; i < NTHREADS; i++)
{
pthread_create( &thread_id[i], NULL, thread_function, NULL );
}
for(j=0; j < NTHREADS; j++)
{
pthread_join( thread_id[j], NULL);
}
return 0;
}
void *thread_function(void *dummyPtr)
{
printf("Thread number %ld addr(errno):%p\n", pthread_self(), &errno);
}
运行这个程序,您可以在每个线程中看到errno的不同地址。在我的机器上运行的输出看起来像:-
Thread number 140672336922368 addr(errno):0x7ff0d4ac0698
Thread number 140672345315072 addr(errno):0x7ff0d52c1698
Thread number 140672328529664 addr(errno):0x7ff0d42bf698
Thread number 140672320136960 addr(errno):0x7ff0d3abe698
Thread number 140672311744256 addr(errno):0x7ff0d32bd698
注意,所有线程的地址都是不同的。
Yes
Errno不再是一个简单的变量,它是一个复杂的幕后变量,特别是为了它是线程安全的。
参见$ man 3 errno:
ERRNO(3) Linux Programmer’s Manual ERRNO(3)
NAME
errno - number of last error
SYNOPSIS
#include <errno.h>
DESCRIPTION
...
errno is defined by the ISO C standard to be a modifiable lvalue of
type int, and must not be explicitly declared; errno may be a macro.
errno is thread-local; setting it in one thread does not affect its
value in any other thread.
我们可以反复检查:
$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$
这是来自<sys/errno.h>在我的Mac:
#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS
所以errno现在是一个__error()函数。函数的实现是线程安全的。