c++ - non-exported variable in module interface unit has multiple addresses - Stack Overflow

I have a variable exception_transformers in coroutine.cpp.If this variable is in an anonymous namespa

I have a variable exception_transformers in coroutine.cpp. If this variable is in an anonymous namespace, then the output shows that register_exception_handler() accesses it at an address 0x18 bytes beyond where every other function accesses it. If exception_transformers is removed from the anonymous namespace, then it is accessed at the same address by all functions.

Is there some interaction between the C++ module and the anonymous namespace that is causing this? Is there some other cause of this problem?


In n4928 (C++23-ish), 9.8.2.2 [namespace.unnamed]

...all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the translation unit.

To me, this indicates that both anonymous namespaces behave as if they have the same name, so having the declaration and definition of transform_exception() in two different anonymous namespaces in the same file shouldn't be causing an issue.


For clang 19:

module;

#include <coroutine>
#include <expected>
#include <functional>
#include <iostream>
#include <ranges>
#include <stdexcept>
#include <system_error>
#include <type_traits>
#include <vector>

export module mycoroutine;
export import :stdexception;

export {
  namespace exco {
  template <typename T> using result_t = std::expected<T, std::error_code>;
  }
}

namespace {
auto transform_exception() -> exco::result_t<void>;
std::vector<std::function<exco::result_t<void>()>> exception_transformers{transform_exception};
}

export {
  namespace exco {
  auto register_exception_handler(std::function<exco::result_t<void>()> func) {
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << '\n';
    exception_transformers.emplace_back(std::move(func));
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << ' '
              << sizeof(exception_transformers) << '\n';
  }
  inline auto unerr(auto const t) {
    return std::unexpected{make_error_code(t)};
  }

  template <typename T> struct expected_wrapper {
    // Initialize as errno 0 so there are no restrictions on T
    // caused by initializing this
    exco::result_t<T> m_result{exco::unerr(static_cast<std::errc>(0))};
    expected_wrapper<T> *&m_ptr_to_this;
    expected_wrapper(expected_wrapper<T> *&ptr_to_this)
        : m_ptr_to_this{ptr_to_this} {
      m_ptr_to_this = this;
    }
    operator result_t<T>() { return std::move(m_result); };
  };
  } // namespace exco

  namespace std {
  template <typename T, typename... Args>
  struct coroutine_traits<exco::result_t<T>, Args...> {
    class promise_type;
    template <typename T1> struct awaiter_type {
      exco::result_t<T1> &m_result;
      explicit awaiter_type(exco::result_t<T1> &result) noexcept
          : m_result{result} {}
      auto await_ready() { return m_result.has_value(); }
      auto await_suspend(std::coroutine_handle<promise_type> h) {
        // This should only happen when await_ready() returns false,
        // which means that has_value() returned false.
        h.destroy();
      }
      auto await_resume() {
        // This should only happen when await_ready() returns true,
        // which means that has_value() returned true.
        return m_result.value();
      }
    };

    class promise_type {
      exco::expected_wrapper<T> *m_ptr_to_wrapper;

    public:
      auto initial_suspend() noexcept -> std::suspend_never { return {}; }
      auto final_suspend() noexcept -> std::suspend_never { return {}; }
      auto return_value(std::error_code ec) {
        m_ptr_to_wrapper->m_result = std::unexpected{ec};
      }
      auto return_value(auto &&t) { m_ptr_to_wrapper->m_result = std::move(t); }
      auto get_return_object() {
        return exco::expected_wrapper<T>{m_ptr_to_wrapper};
      }
      auto unhandled_exception() {
        for (auto &f : exception_transformers | std::views::reverse) {
          try {
            auto result = f();
            if (!result.has_value()) {
              m_ptr_to_wrapper->m_result = std::unexpected{result.error()};
              return;
            }
          } catch (...) {
          }
        }
        m_ptr_to_wrapper->m_result = exco::unerr(exco::stdexception::unknown);
      }
      template <typename T1> auto await_transform(exco::result_t<T1> value) {
        m_ptr_to_wrapper->m_result = std::move(value);
        return awaiter_type<T1>{m_ptr_to_wrapper->m_result};
      }
    };
  };
  } // namespace std
}

namespace {
auto transform_exception() -> exco::result_t<void> {
  std::cout << "transform_exception()" << &exception_transformers << ' '
            << exception_transformers.size() << '\n';
  try {
    throw;
  } catch (std::exception const &) {
    return exco::unerr(exco::stdexception::exception);
  }
}
}

I have a variable exception_transformers in coroutine.cpp. If this variable is in an anonymous namespace, then the output shows that register_exception_handler() accesses it at an address 0x18 bytes beyond where every other function accesses it. If exception_transformers is removed from the anonymous namespace, then it is accessed at the same address by all functions.

Is there some interaction between the C++ module and the anonymous namespace that is causing this? Is there some other cause of this problem?


In n4928 (C++23-ish), 9.8.2.2 [namespace.unnamed]

...all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the translation unit.

To me, this indicates that both anonymous namespaces behave as if they have the same name, so having the declaration and definition of transform_exception() in two different anonymous namespaces in the same file shouldn't be causing an issue.


https://godbolt./z/59EWe5G3b

For clang 19:

module;

#include <coroutine>
#include <expected>
#include <functional>
#include <iostream>
#include <ranges>
#include <stdexcept>
#include <system_error>
#include <type_traits>
#include <vector>

export module mycoroutine;
export import :stdexception;

export {
  namespace exco {
  template <typename T> using result_t = std::expected<T, std::error_code>;
  }
}

namespace {
auto transform_exception() -> exco::result_t<void>;
std::vector<std::function<exco::result_t<void>()>> exception_transformers{transform_exception};
}

export {
  namespace exco {
  auto register_exception_handler(std::function<exco::result_t<void>()> func) {
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << '\n';
    exception_transformers.emplace_back(std::move(func));
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << ' '
              << sizeof(exception_transformers) << '\n';
  }
  inline auto unerr(auto const t) {
    return std::unexpected{make_error_code(t)};
  }

  template <typename T> struct expected_wrapper {
    // Initialize as errno 0 so there are no restrictions on T
    // caused by initializing this
    exco::result_t<T> m_result{exco::unerr(static_cast<std::errc>(0))};
    expected_wrapper<T> *&m_ptr_to_this;
    expected_wrapper(expected_wrapper<T> *&ptr_to_this)
        : m_ptr_to_this{ptr_to_this} {
      m_ptr_to_this = this;
    }
    operator result_t<T>() { return std::move(m_result); };
  };
  } // namespace exco

  namespace std {
  template <typename T, typename... Args>
  struct coroutine_traits<exco::result_t<T>, Args...> {
    class promise_type;
    template <typename T1> struct awaiter_type {
      exco::result_t<T1> &m_result;
      explicit awaiter_type(exco::result_t<T1> &result) noexcept
          : m_result{result} {}
      auto await_ready() { return m_result.has_value(); }
      auto await_suspend(std::coroutine_handle<promise_type> h) {
        // This should only happen when await_ready() returns false,
        // which means that has_value() returned false.
        h.destroy();
      }
      auto await_resume() {
        // This should only happen when await_ready() returns true,
        // which means that has_value() returned true.
        return m_result.value();
      }
    };

    class promise_type {
      exco::expected_wrapper<T> *m_ptr_to_wrapper;

    public:
      auto initial_suspend() noexcept -> std::suspend_never { return {}; }
      auto final_suspend() noexcept -> std::suspend_never { return {}; }
      auto return_value(std::error_code ec) {
        m_ptr_to_wrapper->m_result = std::unexpected{ec};
      }
      auto return_value(auto &&t) { m_ptr_to_wrapper->m_result = std::move(t); }
      auto get_return_object() {
        return exco::expected_wrapper<T>{m_ptr_to_wrapper};
      }
      auto unhandled_exception() {
        for (auto &f : exception_transformers | std::views::reverse) {
          try {
            auto result = f();
            if (!result.has_value()) {
              m_ptr_to_wrapper->m_result = std::unexpected{result.error()};
              return;
            }
          } catch (...) {
          }
        }
        m_ptr_to_wrapper->m_result = exco::unerr(exco::stdexception::unknown);
      }
      template <typename T1> auto await_transform(exco::result_t<T1> value) {
        m_ptr_to_wrapper->m_result = std::move(value);
        return awaiter_type<T1>{m_ptr_to_wrapper->m_result};
      }
    };
  };
  } // namespace std
}

namespace {
auto transform_exception() -> exco::result_t<void> {
  std::cout << "transform_exception()" << &exception_transformers << ' '
            << exception_transformers.size() << '\n';
  try {
    throw;
  } catch (std::exception const &) {
    return exco::unerr(exco::stdexception::exception);
  }
}
}
Share Improve this question edited Mar 6 at 14:53 Graznarak asked Mar 6 at 6:35 GraznarakGraznarak 3,7504 gold badges31 silver badges50 bronze badges 5
  • Unnamed namespaces are a bit special. Your code has two distinct unnamed namespaces, and not two separate sections of the same unnamed namespace. The compiler is tricked into creating two exception_transformers vectors, one in each namespace, as it sees a first instance of the variable in one namespace, while treating the same lines of code as a forward definition in the second unnamed namespace. Whether this is a bug, a feature or plain UB (that would be my guess) is up to the clang people to decide. – Michaël Roy Commented Mar 6 at 10:11
  • In any case, You should post this on the clang forum, this kind of issue definitely deserves a compiler warning. – Michaël Roy Commented Mar 6 at 10:16
  • You fot to post the code. – pptaszni Commented Mar 6 at 10:59
  • @pptaszni There is a link to godbolt in the question – Michaël Roy Commented Mar 6 at 12:00
  • This is probably related to this: wiki.sei.cmu.edu/confluence/display/cplusplus/… The export section in your cpp seems to create a separate compiler/linker unit. – Michaël Roy Commented Mar 6 at 12:53
Add a comment  | 

1 Answer 1

Reset to default 0

Long answer short, accessing a TU-local (Translation Unit local) variable from an exported function in a Module Interface Unit is ill-formed.


I reported this to Clang as this bug report. https://github/llvm/llvm-project/issues/130396

It was marked as a duplicate of this bug report. https://github/llvm/llvm-project/issues/112294

Clang does not currently (19.1.0) report a warning or error for this ill-formed construction. It is expected that it will be implemented in the Clang 21 timeframe.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744993591a4605052.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信