【问题标题】:What is the correct way to build a thread-safe, multiplatform C library?构建线程安全的多平台 C 库的正确方法是什么?
【发布时间】:2013-04-11 09:01:38
【问题描述】:

考虑以下琐碎的 C 程序,

#include <errno.h>
int
main(int argc, char* argv[]) {
  return errno;
}

在 Solaris 上编译时,此代码的行为取决于 -D_REENTRANT 的存在。

solaris$ cc -E test.c | grep return
  return errno;
solaris$ cc -D_REENTRANT -E test.c | grep return
  return  ( * ( ___errno ( ) ) );

后一个版本是线程安全的。如果我们在 Linux 上编译相同的代码,我们会得到独立于 -D_REENTRANT 的相同行为

linux$ gcc -E test.c | grep return
  return (*__errno_location ());
linux$ gcc -D_REENTRANT -E test.c | grep return
  return (*__errno_location ());

Solaris 的cc 具有选项-mt,这意味着-D_REENTRANTgcc-pthread 也是如此。然而,对于一个库来说,指定这些多线程选项似乎很糟糕,因为它对线程运行时注入了不必要的依赖。但是,如果库需要是线程安全的(包括 errno),那么在库和派生代码的编译时都需要线程安全语义。在 Linux 上,这很容易,因为 errno 始终是线程本地的,但正如刚才演示的那样,在其他系统上并不能保证这一点。

这导致了一个问题:线程安全库如何正确编译和分发头文件?一种选择是在主标头中使用#define _REENTRANT,但如果#include &lt;errno.h&gt; 在包含库标头之前出现,则会导致问题。另一种选择是使用-D_REENTRANT 编译库,如果未定义_REENTRANT,则使用主标头#error

制作线程安全库并确保它与链接的代码正确互操作的正确/最佳方法是什么?

【问题讨论】:

    标签: c gcc thread-safety pthreads


    【解决方案1】:

    目前我无法访问任何 Solaris 计算机,因此无法对此进行测试。但是当您将#define _POSIX_C_SOURCE 200112L 作为test.c 的第一行(在包含&lt;errno.h&gt; 之前)时会发生什么?如果您的 Solaris 是 POSIX 兼容的,那么这应该使 errno 扩展为线程安全版本。这是因为 POSIX 将errno 定义如下:

    对于进程的每个线程,errno的值不会受到其他线程对errno的函数调用或赋值的影响。

    因此,它可以移植到任何符合 POSIX 的系统。事实上,如果您想编写符合 POSIX 的应用程序代码,那么您应该始终_POSIX_C_SOURCE 定义为适合您所针对的最低 POSIX 版本的值。在包含任何标题之前,该定义应位于每个源文件的顶部。从 2001 版标准开始:

    严格符合 POSIX 应用程序是一种仅需要 IEEE Std 1003.1-2001 中描述的设施的应用程序。这样的应用:

    ...

    8。对于 C 编程语言,应在包含任何标头之前将 _POSIX_C_SOURCE 定义为 200112L

    【讨论】:

    • cc -D_POSIX_C_SOURCE=200112L -E test.c | grep return --&gt; return ( * ( ___errno ( ) ) ); 确实,按预期工作;您是否还建议在 POSIX 兼容库的标头中定义它?
    • @AlexanderChernyakhovsky:不,它永远不需要在任何头文件中定义。它应该始终定义在源文件的顶部。这是确保在包含任何标题之前定义它的唯一方法。您也可以在命令行上定义它,就像您在示例中所做的那样。这是可以接受的。我更喜欢在源文件中看到它,但这部分只是个人喜好问题。
    • 有趣。听起来您在提倡仍然在我的库标题中检查 #define,因为我仍然不希望最终开发人员最终得到错误的错误号。
    • @AlexanderChernyakhovsky:我认为不值得担心最终开发人员使用错误的 errno。只需确保您的库符合 POSIX 标准。如果最终用户因为不符合 POSIX 标准而遇到麻烦,那么让他们解决问题。没有一种“正确”或可移植的方式来容纳不正确的、不可移植的程序 :) 哎呀,它甚至可能对他们有用。当程序是单线程时,没有什么可以阻止实现让__errno_location() 返回单个静态分配的errno 的位置。
    【解决方案2】:

    如果您的库使用 autoconf,您可能希望使用 AC_USE_SYSTEM_EXTENSIONS 宏。该宏设置了一些特定于目标的定义,这些定义启用了 POSIX +扩展语义。我目前没有要测试的 Solaris 系统,但我相信 _POSIX_PTHREAD_SEMANTICS 应该启用线程安全的 errno。至少它启用了 POSIX _r() 函数,而不是 Solaris 默认提供的 POSIX 草案 _r() 变体。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-27
      • 1970-01-01
      • 2013-12-07
      • 2020-04-04
      • 1970-01-01
      • 2014-10-05
      • 2014-08-19
      • 1970-01-01
      相关资源
      最近更新 更多