在errno.h
中,此变量被声明为extern int errno;
所以我的问题是,在一些调用之后检查errno
值或在多线程代码中使用perror()是否安全。这是一个线程安全变量吗?如果没有,那么替代方案是什么?
我在x86架构上使用linux和gcc。
答案 0 :(得分:165)
是的,它是线程安全的。在Linux上,全局errno变量是特定于线程的。 POSIX要求errno是线程安全的。
请参阅http://www.unix.org/whitepapers/reentrant.html
在POSIX.1中,errno被定义为 外部全局变量。但是这个 定义是不可接受的 多线程环境,因为它 使用会导致不确定性 结果。问题是两个还是 更多线程可能会遇到错误 导致相同的errno被设置。 在这种情况下,一个线程 可能最终会在它之后检查错误 已经被另一个人更新了 线程。
规避结果 非确定性,POSIX.1c重新定义 errno作为可以访问的服务 每线程错误号如下 (ISO / IEC 9945:1-1996,§2.4):
某些函数可能会在访问的变量中提供错误编号 通过符号errno。符号 errno的定义包括 标题,由指定 C标准......对于a的每个线程 过程中,errno的值不得 受函数调用或影响 由其他线程分配给errno。
另见http://linux.die.net/man/3/errno
errno是线程本地的;在一个线程中设置它不会影响其在任何其他线程中的值。
答案 1 :(得分:57)
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 ())); }
$
答案 2 :(得分:11)
在errno.h中,此变量声明为extern int errno;
以下是C标准所说的内容:
宏
errno
不必是对象的标识符。它可能会扩展为函数调用产生的可修改的左值(例如,*errno()
)。
通常,errno
是一个宏,它调用一个函数返回当前线程的错误号的地址,然后取消引用它。
以下是我在Linux上的内容,在/usr/include/bits/errno.h中:
/* 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
最后,它会生成这种代码:
> cat essai.c
#include <errno.h>
int
main(void)
{
errno = 0;
return 0;
}
> gcc -c -Wall -Wextra -pedantic essai.c
> objdump -d -M intel essai.o
essai.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 83 e4 f0 and esp,0xfffffff0
6: e8 fc ff ff ff call 7 <main+0x7> ; get address of errno in EAX
b: c7 00 00 00 00 00 mov DWORD PTR [eax],0x0 ; store 0 in errno
11: b8 00 00 00 00 mov eax,0x0
16: 89 ec mov esp,ebp
18: 5d pop ebp
19: c3 ret
答案 3 :(得分:10)
在许多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) */
答案 4 :(得分:9)
这是来自我的Mac上的<sys/errno.h>
:
#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS
所以errno
现在是一个函数__error()
。该函数是为了线程安全而实现的。
答案 5 :(得分:8)
是,正如errno man page和其他回复中所解释的那样,errno是一个线程局部变量。
然而,有一个很容易被遗忘的愚蠢细节。程序应该在执行系统调用的任何信号处理程序上保存和恢复errno。这是因为信号将由其中一个可能覆盖其值的进程线程处理。
因此,信号处理程序应该保存并恢复errno。类似的东西:
void sig_alarm(int signo)
{
int errno_save;
errno_save = errno;
//whatever with a system call
errno = errno_save;
}
答案 6 :(得分:6)
我认为答案是“它取决于”。线程安全的C运行时库通常将errno实现为函数调用(宏扩展到函数),如果您使用正确的标志构建线程代码。
答案 7 :(得分:2)
我们可以通过在计算机上运行一个简单程序来进行检查。
myCandleStickChartView.renderer = MyCandleStickChartRenderer(view: myCandleStickChartView, minValue: 400, maxValue: 1450)
运行该程序,您可以在每个线程中看到errno的不同地址。我的机器上运行的输出看起来像:-
#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);
}
请注意,所有线程的地址都不同。