[PATCH] libstdc++: Make basic_stringbuf use initial SSO capacity [PR80676]

Jonathan Wakely jwakely@redhat.com
Fri Oct 31 18:00:32 GMT 2025


On Mon, 27 Oct 2025 at 22:46 -0400, Patrick Palka wrote:
>From: Jonathan Wakely <jwakely@redhat.com>
>
>Tested on x86_64-pc-linux-gnu.  Patch originally written by Jonathan and
>completed by me (mainly testsuite adjustments).

Thanks for taking on the task of figuring out what needing changing in
the tests. IIRC I made the changes to stringbuf and when tests failed
in unexpected ways I got confused and gave up.

>I don't understand the stringbuf API enough to determine what the
>correct value of egptr() - eback() (i.e. the get area size) should be in
>the 26250.cc tests aftering calling overflow().  It's currently zero
>after this patch, which I'm pretty sure is wrong?
>
>[stringbuf.virtuals]/8 says: If ios_base::in is set in mode, the
>function alters the read end pointer egptr() to point just past the new
>write position.
>
>In light of that I guess egptr() - eback() should still be 1 after this
>patch then?  That'd require making stringbuf::overflow() more
>consistently set egptr() I believe.

As written, I think the "alters the read end pointer egptr()" wording
only applies in the case where overflow needs to make a new write
position available, i.e. needs to reallocate.

When we've been initialized with the SSO buffer the initial capacity
means that the single call to overflow in 26250.cc doesn't need to
reallocate.  So I think it's correct that after your change, the size
of the get area isn't change by the overflow('x') call. Because it
doesn't reallocate.

>But then what about for:
>
>  buf.sputn("01234567890abcdef", 16); // Exceed SSO buffer
>  buf.overflow('x');
>  VERIFY( buf.egptr() - buf.eback() == ? );
>
>I would think the get area size should be 17? But other implementations
>including us before this patch say 16, so I'm pretty confused.

Again, I think this depends on whether overflow('x') reallocates. If
the capacity after sputn means a write position is available, then my
reading of the spec says we don't change egptr().

But that doesn't really make sense to me. Surely the point of moving
the egptr is that a character has been written to the put area and
that character should now be available for reading, so the get area
should expand to include the just-written character. But that should
happen any time a character is written to the put area, not only when
reallocation was needed?

I'll do some experimenting and refresh my understanding of this
wording and what we do. But I think your patch is fine for trunk now,
with some comment changes requested below confusedconstructible_from

>-- >8 --
>
>LWG 2995 allows us to use the SSO std::string's short string buffer as
>the initial put area for a stringbuf. This avoids a call to overflow for
>the first char written to an ostringstream, because the put area
>consists of 15 chars immediately after construction.
>
>	PR libstdc++/80676
>
>libstdc++-v3/ChangeLog:
>
>	* include/std/sstream (basic_stringbuf::basic_stringbuf())
>	[_GLIBCXX_USE_CXX11_ABI]: Call _M_stringbuf_init to use SSO buffer.
>	(basic_stringbuf::basic_stringbuf(openmode))
>	[_GLIBCXX_USE_CXX11_ABI]: Likewise.
>	* testsuite/27_io/basic_stringbuf/cons/80676.cc: New.
>	* testsuite/27_io/basic_stringbuf/cons/char/1.cc: Adjust call to
>	check_pointers.
>	* testsuite/27_io/basic_stringbuf/cons/wchar_t/1.cc: Likewise.
>	* testsuite/27_io/basic_stringbuf/overflow/char/26250.cc
>	[_GLIBCXX_USE_CXX11_ABI]: Adjust get area size assert.
>	* testsuite/27_io/basic_stringbuf/overflow/wchar_t/26250.cc
>	[_GLIBCXX_USE_CXX11_ABI]: Likewise.
>	* testsuite/27_io/basic_stringbuf/sputc/char/9404-1.cc
>	[_GLIBCXX_USE_CXX11_ABI]: Don't expected overflow() to be
>	called.
>	* testsuite/27_io/basic_stringbuf/sputc/wchar_t/9404-1.cc
>	[_GLIBCXX_USE_CXX11_ABI]: Likewise.
>	* testsuite/27_io/basic_stringbuf/sputn/char/9404-2.cc
>	[_GLIBCXX_USE_CXX11_ABI]: Likewise.
>	* testsuite/util/testsuite_io.h (constraint_buf::check_pointers):
>	Add defaulted is_stringbuf bool parameter.  Expect non-null
>	instead of null pointers when DR 2995 applies.
>
>Co-authored-by: Patrick Palka <ppalka@redhat.com>
>---
> libstdc++-v3/include/std/sstream              | 12 +++++++--
> .../27_io/basic_stringbuf/cons/80676.cc       | 26 +++++++++++++++++++
> .../27_io/basic_stringbuf/cons/char/1.cc      |  2 +-
> .../27_io/basic_stringbuf/cons/wchar_t/1.cc   |  2 +-
> .../basic_stringbuf/overflow/char/26250.cc    |  4 +++
> .../basic_stringbuf/overflow/wchar_t/26250.cc |  4 +++
> .../basic_stringbuf/sputc/char/9404-1.cc      |  4 +++
> .../basic_stringbuf/sputc/wchar_t/9404-1.cc   |  4 +++
> .../basic_stringbuf/sputn/char/9404-2.cc      |  4 +++
> libstdc++-v3/testsuite/util/testsuite_io.h    | 10 ++++++-
> 10 files changed, 67 insertions(+), 5 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/80676.cc
>
>diff --git a/libstdc++-v3/include/std/sstream b/libstdc++-v3/include/std/sstream
>index b1b41260ce3d..10fbaf00c084 100644
>--- a/libstdc++-v3/include/std/sstream
>+++ b/libstdc++-v3/include/std/sstream
>@@ -128,7 +128,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
>       */
>       basic_stringbuf()
>       : __streambuf_type(), _M_mode(ios_base::in | ios_base::out), _M_string()
>-      { }
>+      {
>+#if _GLIBCXX_USE_CXX11_ABI
>+	_M_stringbuf_init(_M_mode);
>+#endif
>+      }
>
>       /**
>        *  @brief  Starts with an empty string buffer.
>@@ -140,7 +144,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
>       explicit
>       basic_stringbuf(ios_base::openmode __mode)
>       : __streambuf_type(), _M_mode(__mode), _M_string()
>-      { }
>+      {
>+#if _GLIBCXX_USE_CXX11_ABI
>+	_M_stringbuf_init(__mode);
>+#endif
>+      }
>
>       /**
>        *  @brief  Starts with an existing string buffer.
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/80676.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/80676.cc
>new file mode 100644
>index 000000000000..efdac03c366d
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/80676.cc
>@@ -0,0 +1,26 @@
>+// { dg-do run { target c++11 } }
>+// { dg-require-effective-target cxx11_abi }
>+
>+// PR libstdc++/80676
>+// [DR 2995] basic_stringbuf does not use initial capacity of SSO string
>+
>+#include <sstream>
>+#include <testsuite_hooks.h>
>+
>+int counter;
>+
>+struct SB : std::stringbuf
>+{
>+  int_type overflow(int_type c) override
>+  {
>+    ++counter;
>+    return std::stringbuf::overflow(c);
>+  }
>+};
>+
>+int main()
>+{
>+  SB sb;
>+  sb.sputc('a');
>+  VERIFY( counter == 0 );
>+}
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/char/1.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/char/1.cc
>index 78aca7730136..43f8bd1c933d 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/char/1.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/char/1.cc
>@@ -27,7 +27,7 @@
> void test01()
> {
>   __gnu_test::constraint_stringbuf sbuf;
>-  VERIFY( sbuf.check_pointers() );
>+  VERIFY( sbuf.check_pointers(/*is_stringbuf=*/true) );
> }
>
> void test02()
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/wchar_t/1.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/wchar_t/1.cc
>index 5d40df9fe395..aa833026aff4 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/wchar_t/1.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/cons/wchar_t/1.cc
>@@ -27,7 +27,7 @@
> void test01()
> {
>   __gnu_test::constraint_wstringbuf sbuf;
>-  VERIFY( sbuf.check_pointers() );
>+  VERIFY( sbuf.check_pointers(/*is_stringbuf=*/true) );
> }
>
> void test02()
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/char/26250.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/char/26250.cc
>index 3cb3adea70b7..1b80857d2520 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/char/26250.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/char/26250.cc
>@@ -45,7 +45,11 @@ void test01()
>   VERIFY( write_positions > 1 );
>
>   // 27.7.1.3, p8:

Could you please update these comments (and the ones on line 18) to
make it clear what "27.7.1.x" refers to, so:

// C++03 27.7.1.3 [lib.stringbuf.virtuals] p8:

and on line 18 fix the section number so it's not completely wrong!

// C++11 27.8.2.4 [stringbuf.virtuals] Overridden virtual functions

Or harmonize them to both refer to C++11, just as long as we don't
have naked subclause numbers which are no use without saying which
standard they come from.

>+#if _GLIBCXX_USE_CXX11_ABI
>+  VERIFY( buf.egptr() - buf.eback() == 0 );
>+#else
>   VERIFY( buf.egptr() - buf.eback() == 1 );
>+#endif
> }
>
> int main()
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/wchar_t/26250.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/wchar_t/26250.cc
>index be0bc14e5055..75de48bc84f1 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/wchar_t/26250.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/overflow/wchar_t/26250.cc
>@@ -45,7 +45,11 @@ void test01()
>   VERIFY( write_positions > 1 );
>
>   // 27.7.1.3, p8:
>+#if _GLIBCXX_USE_CXX11_ABI
>+  VERIFY( buf.egptr() - buf.eback() == 0 );
>+#else
>   VERIFY( buf.egptr() - buf.eback() == 1 );
>+#endif
> }
>
> int main()
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/char/9404-1.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/char/9404-1.cc
>index d5481eacfb3b..05d3bee8d499 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/char/9404-1.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/char/9404-1.cc
>@@ -53,7 +53,11 @@ void test04()
>   Derived_stringbuf dsbuf_01;
>   over_called = false;
>   dsbuf_01.sputc('i');
>+#if _GLIBCXX_USE_CXX11_ABI
>+  VERIFY( !over_called ); // DR 2995 lets us strengthen this
>+#else
>   VERIFY( over_called );
>+#endif
>   over_expected = dsbuf_01.pub_epptr() == dsbuf_01.pub_pptr();
>   over_called = false;
>   dsbuf_01.sputc('v');
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/wchar_t/9404-1.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/wchar_t/9404-1.cc
>index 48bcefc312e9..18f86d8f87e8 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/wchar_t/9404-1.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputc/wchar_t/9404-1.cc
>@@ -53,7 +53,11 @@ void test04()
>   Derived_stringbuf dsbuf_01;
>   over_called = false;
>   dsbuf_01.sputc(L'i');
>+#if _GLIBCXX_USE_CXX11_ABI
>+  VERIFY( !over_called ); // DR 2995 lets us strengthen this
>+#else
>   VERIFY( over_called );
>+#endif
>   over_expected = dsbuf_01.pub_epptr() == dsbuf_01.pub_pptr();
>   over_called = false;
>   dsbuf_01.sputc(L'v');
>diff --git a/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputn/char/9404-2.cc b/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputn/char/9404-2.cc
>index bd0aa86e03cf..a7b06f825d6a 100644
>--- a/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputn/char/9404-2.cc
>+++ b/libstdc++-v3/testsuite/27_io/basic_stringbuf/sputn/char/9404-2.cc
>@@ -54,7 +54,11 @@ void test04()
>   Derived_stringbuf dsbuf_02;
>   over_called = false;
>   dsbuf_02.sputn("sonne's", 7);
>+#if _GLIBCXX_USE_CXX11_ABI
>+  VERIFY( !over_called ); // DR 2995 lets us strengthen this
>+#else
>   VERIFY( over_called );
>+#endif
>   over_expected = dsbuf_02.pub_epptr() == dsbuf_02.pub_pptr();
>   over_called = false;
>   dsbuf_02.sputn(" peak", 5);
>diff --git a/libstdc++-v3/testsuite/util/testsuite_io.h b/libstdc++-v3/testsuite/util/testsuite_io.h
>index f4dca68117d6..797d11c905b8 100644
>--- a/libstdc++-v3/testsuite/util/testsuite_io.h
>+++ b/libstdc++-v3/testsuite/util/testsuite_io.h
>@@ -68,7 +68,7 @@ namespace __gnu_test
>       }
>
>       bool
>-      check_pointers()
>+      check_pointers(bool is_stringbuf = false)
>       {
> 	bool one   = this->eback() == 0;
> 	bool two   = this->gptr() == 0;
>@@ -77,6 +77,14 @@ namespace __gnu_test
> 	bool four  = this->pbase() == 0;
> 	bool five  = this->pptr() == 0;
> 	bool six   = this->epptr() == 0;
>+#if _GLIBCXX_USE_CXX11_ABI
>+	if (is_stringbuf)
>+	  // DR 2995 made it implementation-defined whether the sequence pointers
>+	  // (eback(), gptr(), egptr(), pbase(), pptr(), epptr()) are initialized
>+	  // to null pointers.  We initialize them to the underlying string's
>+	  // SSO buffer.
>+	  return !one && !two && !three && !four && !five && !six;
>+#endif
> 	return one && two && three && four && five && six;
>       }
>     };
>-- 
>2.51.1.549.g4e98b730f1
>
>



More information about the Libstdc++ mailing list