https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128040

            Bug ID: 128040
           Summary: [modules] std::string::npos defined as a strong global
                    symbol in every TU importing the <iomanip> header unit
                    at -O1+ in C++/20 ("multiple definition" at link time)
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: oleg at smolsky dot net
  Target Milestone: ---

A TU that imports the <iomanip> header unit and odr-uses std::string::npos
emits a non-COMDAT, non-unique global definition of npos at -O1 and -O2, with
-std=c++17 or -std=c++20. Linking two such objects fails:

    multiple definition of `std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char> >::npos'

With a textual #include <iomanip> the same TUs reference libstdc++'s
definition, which comes from its explicit instantiation of basic_string<char>:

    nm x.o | grep npos:   #include <iomanip>   import <iomanip>;
    -O0                    u                    u
    -O1                    U                    R    <- GLOBAL OBJECT in
.rodata
    -O2                    U                    R

The trigger is narrower than "any std header unit". A header unit of <string>
alone is fine at -O2 (npos stays U). <iomanip>, or any header unit that
contains it, e.g. <bits/stdc++.h>, is not. We hit this through the
<bits/stdc++.h> header unit that include translation uses for every standard
#include.

Reproducer (GCC 16.2.0, configured with ../configure --prefix=/opt/gcc-16
--enable-languages=c,c++ --enable-gold=default --disable-multilib
--with-zstd=no):

$ cat x.cc
import <iomanip>;
const std::string::size_type *x() { return &std::string::npos; }

$ cat y.cc
import <iomanip>;
const std::string::size_type *x();
int main() { return x() == &std::string::npos ? 0 : 1; }

$ g++ -std=c++20 -O2 -fmodules -x c++-system-header iomanip
$ g++ -std=c++20 -O2 -fmodules -c x.cc
$ g++ -std=c++20 -O2 -fmodules -c y.cc
$ g++ x.o y.o
.../x86_64-pc-linux-gnu/bin/ld: error: y.o: multiple definition of
'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>
>::npos'
.../x86_64-pc-linux-gnu/bin/ld: x.o: previous definition here
collect2: error: ld returned 1 exit status

(The linker is gold, from --enable-gold=default.)

$ readelf -sW x.o | grep 4nposE
     4: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    5
_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4nposE

Expected: as with #include <iomanip>, an undefined reference at -O1/-O2
(libstdc++ provides npos through its explicit instantiation of
basic_string<char>), or at worst a COMDAT/unique definition that the linker
merges.

Standard and optimization levels, with import <iomanip>:

    -std=c++17 -O2   R   (fails)
    -std=c++20 -O1   R   (fails)
    -std=c++20 -O2   R   (fails)
    -std=c++23 -O2   u   (links)
    -std=c++20 -O0   u   (links)

Reply via email to