使用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
相关产品推荐
相关产品推荐

