You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用optional<string>触发GCC编译警告,是否为GCC的bug?

GCC的-Wmaybe-uninitialized警告是否为误报?

我将代码精简后,在x64 12.3版本的g以及Godbolt测试的GCC 14.1中,使用-O3、C20编译会失败;但Clang编译无任何警告。此外还有一个特殊触发条件:如果把func()的函数体直接放到main()中,就不会出现该警告。想确认这个警告是否有效,还是GCC存在bug。

精简后的测试代码

#include <cassert>
#include <cstdlib>
#include <optional>
#include <string>
#include <utility>

using namespace std;

class HS {
   public:
    constexpr HS() = default;

    constexpr explicit HS(string_view view) : _view(view) {}

    constexpr HS(HS const& other) { *this = other; }

    constexpr HS& operator=(HS const& other) {
        _stringOpt = other._stringOpt;
        _view = _stringOpt ? string_view{*_stringOpt} : other._view;
        return *this;
    }

    constexpr HS& operator=(HS&& other) noexcept {
        if (this != &other) {
            _stringOpt =
                std::exchange(/*inout*/ other._stringOpt, std::nullopt);
            _view = _stringOpt ? string_view{*_stringOpt} : other._view;
            other._view = k_empty;
        }
        return *this;
    }

   private:
    static constexpr char const* k_empty = "";

    optional<string> _stringOpt;
    string_view _view = k_empty;
};

inline HS operator""_hs(char const* string, size_t length) noexcept {
    return HS(string_view{string, length});
}

HS bar(HS const& hs) { return rand() < 10000 ? "sdok"_hs : hs; }

int func() {
    HS foo;
    try {
        foo = "1"_hs;
        foo = bar(foo);        
        foo = "2"_hs;  // <--- 警告指向这一行
    } catch (...) {
        return 1;
    }
    return 0;
}

// 把func()的函数体放到main()中就不会触发警告
int main() { return func(); }

编译错误(警告被视为错误)

In file included from /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/string:54,
                 from <source>:5:
In member function 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]',
    inlined from 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator __sv_type() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/basic_string.h:950:16,
    inlined from 'constexpr HS& HS::operator=(HS&&)' at <source>:28:57,
    inlined from 'constexpr HS& HS::operator=(HS&&)' at <source>:24:19,
    inlined from 'int func()' at <source>:52:15:
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/basic_string.h:1077:16: error: '*(const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*)((char*)&foo + offsetof(HS, HS::_stringOpt.std::optional<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>::<unnamed>.std::_Optional_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, false, false>::<unnamed>)).std::__cxx11::basic_string<char>::_M_string_length' may be used uninitialized [-Werror=maybe-uninitialized]
 1077 |       { return _M_string_length; }
      |                ^~~~~~~~~~~~~~~~
<source>: In function 'int func()':
<source>:48:8: note: 'foo' declared here
   48 |     HS foo;
      |        ^~~
In member function 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::pointer std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_M_data() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]',
    inlined from 'constexpr const _CharT* std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::data() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/basic_string.h:2654:23,
    inlined from 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator __sv_type() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/basic_string.h:950:16,
    inlined from 'constexpr HS& HS::operator=(HS&&)' at <source>:28:57,
    inlined from 'constexpr HS& HS::operator=(HS&&)' at <source>:24:19,
    inlined from 'int func()' at <source>:52:15:
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/basic_string.h:228:28: error: '*(const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*)((char*)&foo + offsetof(HS, HS::_stringOpt.std::optional<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>::<unnamed>.std::_Optional_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, false, false>::<unnamed>)).std::__cxx11::basic_string<char>::_M_dataplus.std::__cxx11::basic_string<char>::_Alloc_hider::_M_p' may be used uninitialized [-Werror=maybe-uninitialized]
  228 |       { return _M_dataplus._M_p; }
      |                            ^~~~
<source>: In function 'int func()':
<source>:48:8: note: 'foo' declared here
   48 |     HS foo;
      |        ^~~
cc1plus: all warnings being treated as errors
Compiler returned: 1

结论:这是GCC的误报

这是GCC的-Wmaybe-uninitialized检查出现了误报。原因如下:

  • 当执行foo = "2"_hs时,调用的是HS的移动赋值运算符。"2"_hs的_stringOpt是未激活状态(因为对应的构造函数只初始化了_view,没有给_stringOpt赋值)。
  • 在移动赋值逻辑中,_stringOpt = std::exchange(other._stringOpt, std::nullopt)会把_stringOpt置为std::nullopt,此时后续的_view = _stringOpt ? string_view{*_stringOpt} : other._view会走other._view分支,完全不会访问_stringOpt内部的std::string成员。
  • GCC的数据流分析没有正确追踪到_stringOpt的状态,错误认为代码会访问未初始化的std::string内部变量(_M_string_length和_M_p),从而触发了警告。

另外,把func()的函数体移到main()中警告消失,是因为函数内联后GCC的分析范围变化,能更准确地追踪变量的初始化状态,避免了误判。

内容的提问来源于stack exchange,提问作者Charles Savoie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:32:04