Search code examples
c++c++11global-variables

Extern global variable usage in Standard Library


  1. Global Variables are usually discouraged. What is the rational behind Global Variables with external linkage in C++ Standard Library?

  2. Is it true that extern variable is only declaration but not definition?

An example is the gcc implementation of std::call_once in mutex.h Thread local Global Variables with external linkage are declared:

  extern __thread void* __once_callable;
  extern __thread void (*__once_call)();

in

  /// @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
  1. If __once_callable & __once_call are declared but not defined, where are they defined? I didn't find definitions of them in Standard Library header files in devtoolset-11 without extern. Are they defined in source files?

Solution

  • Part 1 - Rational behind global variables in the C++ standard library

    That's true that as a general rule of thumb, global variables should be used with extreme care. That's because in a large program it's almost impossible to track which parts of the program modify the variable and it quickly becomes impossible to understand and debug what's happening (more reasons can be found in answers to this question).

    That said, global variables are a legitimate tool that can be used as long as care is taken. These even might have some advantages, for example in performance. Basically, it's OK to use global variable if you ensure that the number of places modifying the variable is strictly limited. In this case, the variable is internal to the standard library, only accessed in the mutex file and isn't supposed to be accessed by any other code, so that's why it is fine.

    As to why they had to use a global variable in this specific case, it's because, frankly, there was no other choice. This variable is used to wrap the callable in this function and pass it to the __gthread_once() function (line 907). __gthread_once() only accepts a pointer to global function without any arguments as parameter (here __once_proxy). For __once_proxy() to invoke the "callable", it had to be stored at some known place. And without any arguments, the only option is using a global variable.

    Basically, there are three kinds of memory where you can allocate variables: heap, stack and statically allocated variables area of the executable (.bss). The "global" variables are allocated in the later. The addresses of global variables are filled directly into the executable code by the linker (or OS loader). While the addresses of variables on stack or heap depend on a lot of factors and to access something specific there, its address has to be passed as a parameter (pointer), but as mentioned __once_proxy() receives no parameters, that's why stack and heap weren't an option here.

    Part 2 - extern variables (declaration vs definition)

    Regarding the second part of your question - yes extern only declares a variable. A definition has to exist in some place. For these variables it's in mutex.cc as @Jason pointed in his answer.