标准库中外部全局变量的用法

cpp*_*cpp 2 c++ global-variables c++11

  1. 全局变量通常不被鼓励。C++ 标准库中具有外部链接的全局变量背后的原理是什么?

  2. extern变量真的只是声明而不是定义吗?

std::call_once一个例子是声明具有mutex.h 外部链接的线程局部全局变量的 gcc 实现:

  extern __thread void* __once_callable;
  extern __thread void (*__once_call)();
Run Code Online (Sandbox Code Playgroud)

  /// @cond undocumented
# ifdef _GLIBCXX_HAVE_TLS
  // If TLS is available use thread-local state for the type-erased callable
  // that is being run by std::call_once in the current thread.
  extern __thread void* __once_callable;
  extern __thread void (*__once_call)();

  // RAII type to set up state for pthread_once call.
  struct once_flag::_Prepare_execution
  {
    template<typename _Callable>
      explicit
      _Prepare_execution(_Callable& __c)
      {
    // Store address in thread-local pointer:
    __once_callable = std::__addressof(__c);
    // Trampoline function to invoke the closure via thread-local pointer:
    __once_call = [] { (*static_cast<_Callable*>(__once_callable))(); };
      }

    ~_Prepare_execution()
    {
      // PR libstdc++/82481
      __once_callable = nullptr;
      __once_call = nullptr;
    }

    _Prepare_execution(const _Prepare_execution&) = delete;
    _Prepare_execution& operator=(const _Prepare_execution&) = delete;
  };
# else
  // Without TLS use a global std::mutex and store the callable in a
  // global std::function.
  extern function<void()> __once_functor;

  extern void
  __set_once_functor_lock_ptr(unique_lock<mutex>*);

  extern mutex&
  __get_once_mutex();

  // RAII type to set up state for pthread_once call.
  struct once_flag::_Prepare_execution
  {
    template<typename _Callable>
      explicit
      _Prepare_execution(_Callable& __c)
      {
    // Store the callable in the global std::function
    __once_functor = __c;
    __set_once_functor_lock_ptr(&_M_functor_lock);
      }

    ~_Prepare_execution()
    {
      if (_M_functor_lock)
    __set_once_functor_lock_ptr(nullptr);
    }

  private:
    // XXX This deadlocks if used recursively (PR 97949)
    unique_lock<mutex> _M_functor_lock{__get_once_mutex()};

    _Prepare_execution(const _Prepare_execution&) = delete;
    _Prepare_execution& operator=(const _Prepare_execution&) = delete;
  };
# endif
Run Code Online (Sandbox Code Playgroud)
  1. 如果__once_callable&__once_call已声明但未定义,那么它们在哪里定义?我没有在devtoolset-11without的标准库头文件中找到它们的定义extern。它们是在源文件中定义的吗?

vvv*_*444 5

第 1 部分 - C++ 标准库中全局变量背后的原理

确实,作为一般经验法则,全局变量的使用应该非常小心。这是因为在大型程序中,几乎不可能跟踪程序的哪些部分修改了变量,并且很快就无法理解和调试正在发生的事情(更多原因可以在这个问题的答案中找到

也就是说,全局变量是一个合法的工具,只要小心就可以使用。这些甚至可能有一些优势,例如在性能方面。基本上,如果您确保严格限制修改变量的位置数量,则可以使用全局变量。在这种情况下,该变量是标准库的内部变量,只能在mutex文件中访问,并且不应由任何其他代码访问,因此这就是为什么它没问题。

至于为什么在这种特定情况下他们必须使用全局变量,坦率地说,因为没有其他选择。该变量用于包装该函数中的可调用对象并将其传递给该__gthread_once()函数(第 907 行)。__gthread_once()只接受指向全局函数的指针,不带任何参数作为参数(此处__once_proxy)。为了__once_proxy()调用“可调用”,它必须存储在某个已知的位置。如果没有任何参数,唯一的选择是使用全局变量。

基本上,您可以在三种类型的内存中分配变量:堆、堆栈和可执行文件的静态分配变量区域(.bss)。“全局”变量在后面分配。全局变量的地址由链接器(或操作系统加载器)直接填充到可执行代码中。虽然堆栈或堆上变量的地址取决于很多因素,并且要访问那里的特定内容,它的地址必须作为参数(指针)传递,但如上所述不__once_proxy()接收任何参数,这就是堆栈和堆不接收任何参数的原因这里有一个选项。

第 2 部分 -extern变量(声明与定义)

关于您问题的第二部分 - 是的extern仅声明一个变量。定义必须存在于某个地方。对于这些变量,正如mutex.cc@Jason 在他的回答中指出的那样。