C errno线程安全吗?

C errno线程安全吗?,c,linux,multithreading,gcc,C,Linux,Multithreading,Gcc,在errno.h中,此变量声明为extern int errnotest.c #包括 f(){g(errno);} $cc-E test.c | grep^f f(){g((*uu errno_location());} $ 我认为答案是“视情况而定”。线程安全的C运行时库通常将errno作为函数调用(宏扩展为函数)来实现,如果您正在使用正确的标志构建线程代码。在许多Unix系统上,使用-D_可重入进行编译可确保errno是线程安全的 例如: #if defined(_REENTRANT) |

errno.h
中,此变量声明为
extern int errnoerrno
值或者在多线程代码中使用peror()是否安全。这是线程安全变量吗?如果没有,那么还有什么选择


我在x86架构上使用带gcc的linux

是的,它是线程安全的。在Linux上,全局errno变量是线程特定的。POSIX要求errno是线程安全的

在POSIX.1中,errno被定义为 外部全局变量。但是这个 这一定义在某种意义上是不可接受的 多线程环境,因为 使用会导致不确定性 结果。问题是有两三个 更多线程可能会遇到错误,所有 导致设置相同的错误号。 在这种情况下,一根线 可能会在它之后检查errno 已由其他用户更新 线

规避由此产生的风险 不确定性,POSIX.1c重新定义 errno作为可以访问 每个线程的错误数如下 (ISO/IEC 9945:1-1996,§2.4):

某些函数可能在变量中提供错误号 通过符号errno。象征 errno是通过包含 标题,由 C标准。。。对于每个线程的 过程中,errno的值不应 受函数调用或 其他线程对errno的赋值

也看到

errno是线程本地的;在一个线程中设置它不会影响它在任何其他线程中的值


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.
ERRNO(3)Linux程序员手册ERRNO(3)
名称
errno-上次错误的数目
提要
#包括
描述
...
ISO C标准将errno定义为可修改的
类型int,并且不能显式声明;errno可能是一个宏。
errno是线程本地的;在一个线程中设置它不会影响其性能
任何其他线程中的值。

我们可以再次检查:

$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$ 
$cat>test.c
#包括
f(){g(errno);}
$cc-E test.c | grep^f
f(){g((*uu errno_location());}
$ 

我认为答案是“视情况而定”。线程安全的C运行时库通常将errno作为函数调用(宏扩展为函数)来实现,如果您正在使用正确的标志构建线程代码。

在许多Unix系统上,使用
-D_可重入
进行编译可确保
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) */
这是我的Mac电脑上的

#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS
#包括
__开始
外部输入*错误(无效);
#定义errno(*\uuu error())
__十二月底
因此,
errno
现在是一个函数
\uu error()
。该函数的实现是为了线程安全

在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
>cat essai.c
#包括
int
主(空)
{
errno=0;
返回0;
}
>gcc-c-Wall-Wextra-pedantic essai.c
>objdump-d-M intel essai.o
o:文件格式elf32-i386
第节的分解。正文:
00000000 :
0:55推ebp
1:89 e5 mov ebp,esp
3:83 e4 f0和esp,0xfffffff0
6:e8 fc ff呼叫7;获取EAX中errno的地址
b:c700 mov德沃德PTR[eax],0x0;在errno中存储0
11:B800 mov eax,0x0
16:89 ec mov esp,ebp
18:5d pop ebp
19:c3 ret

,正如和其他回复中所解释的,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);                                                                                       
}

请注意,所有线程的地址都不同。

真的吗?他们什么时候做的?当我在做C编程时,信任errno是一个大问题。@vinit:errno实际上是在bits/errno.h中定义的。阅读包含文件中的注释。它说:“声明'errno'变量,除非它被bits/errno.h定义为宏。GNU就是这种情况,它是每线程变量。使用宏的这种重新声明仍然有效,但它将是一个没有原型的函数声明,可能会触发-Wstrict prototype警告。”@vinit,它已经为您声明过了。你不需要做任何事。
extern int errno
变量声明被包装在一个
#ifndef errno
条件中,该条件将为false,因为errno已被定义为bits/errno.h中的宏。如果您使用的是Linux 2.6,则无需执行任何操作。只需开始编程。:-)@vinit dhatrak应该有
#如果!已定义的|已定义的|可重入的
,|在编译普通程序时未定义LIBC。无论如何,运行echo
#include'| gcc-E-dM-xc-
,看看有无-pthread的区别。errno是
#在这两种情况下都定义errno(*u errno_location())
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