Document number |
P3824R0 |
Date |
2025-09-06 |
Reply-to |
Jarrad J. Waterloo <descender76 at gmail dot com>
|
Audience |
Core Working Group Evolution Working Group SG23: Safety and Security |
Static storage for braced initializers NBC examples
Table of contents
Abstract
Require static storage duration
in Static storage for braced initializers
so that more memory unsafety will not be added to the C++
language and thus ensure that this feature will be utilized instead of being bypassed for legacy code that does offer such assurances.
Motivation
The problem
Without Static storage for braced initializers
the programmer has to perform a work around as stated in 2.1. Workarounds
section.
"This code creates a 2MB backing array on the stack frame of the function that initializes v :"
"This code does not:"
"So the latter is a workaround. But it shouldn’t be necessary to work around this issue; we should fix it instead."
|
Unfortuntely, with this feature, as currently worded, the programmer still has to do the work around.
9.5.5 List-initialization [dcl.init.list] Paragraph 6
makes no mention of static storage duration
in the specification. It only has an example of what a compiler might do.
This is further confirmed in a footnote at the end of the standard.
C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2
"Permit the implementation to store backing arrays in static read-only memory."
Permit is not a guarantee and consequently is less than the guarantee made in the workaround where the static
keyword is used.
Without a requirement, this feature not only fails to provide the desired performance goals but also fails by increasing inconsistency, ambiguity and unsafety in the language. Consider the following example.
This is actually two examples of potential dangling references, rilm
and pilm
, being created; both by return and by passthrough. If the compiler choose to make the lifetimes of the backing arrays of the initializer lists to have static storage duration
than rilm
and pilm
are memory safe, race free and thread safe. If the compiler chooses automatic storage duration
than rilm
and pilm
are dangling references.
If the programmer thinks that their code has static storage duration
because it was based on the provided example in the standard and the compiler left it as automatic storage duration
then the programmer likely did not make the necessary code changes to ensure it doesn’t dangle. Not being able to trust this feature will cause programmers to not trust their compilers and to program with the esoteric legacy workaround code, thus defeating the purpose of having this feature in the first place.
A programmer can’t be expected to be responsible for dangling, if the programmer can’t definitely reason about the lifetimes of objects. A code reviewer/code auditor can’t be expected to find dangling, if the reviewer/auditor can’t definitely reason about the lifetimes of objects. None of these three people should be forced to look at assembly, a different programming language, to know what the compiler decided to do in this instant and worse to repeat the process for each initializer list/braced initializer in a program.
Without definite verbiage in the standard, this is a non portable language feature between compilers. Worse yet, it may not be portable/stable within a single compiler as the behavior could vary with respect to different compiler flags.
Besides failing to require static storage duration
the current verbiage fails to even mention the possibility of static storage duration
.
"The backing array has the same lifetime as any other temporary object (6.8.7), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary." 9.5.5 List-initialization [dcl.init.list] Paragraph 6
Except that it is not exactly the same because when the lifetime of a temporary is extended it still has automatic storage duration
changed from the statement to the block, like a declared variable.
C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2
What is proposed is different because it is being extended by being turned into something that has static storage direction
.
The solution
The lifetime of the backing array associated with initializer lists needs to definitely state static storage duration
in a similar fashion as stated else where in the standard. Consider the very first occurence of static storage duration
in the standard.
5.13.5 String literals [lex.string] Paragraph 9
"Evaluating a string-literal results in a string literal object with static storage duration (6.8.6)."
Should a initializer list of ints or even chars be specified radically nebulous from the clear straight forward verbiage of string literals. Fixing the initializer list verbiage with the string literal verbiage would make the two more consistent and in a reasonable fashion.
Even the example provided by the standard could benefit from some revisions.
9.5.5 List-initialization [dcl.init.list] Paragraph 6
The current example could be expanded to better illustrate when this feature does kick in versus may kick in.
While it would be ideal if both h
and i
examples had static storage duration
, since f({1, x, x, x, 3});
requires analysis of additional lines of code it could make sense, on the short term, that it MAY have static storage duration
. Hopefully, even this scenario would be strengthened in future C++
releases since it is needed for the deduplication of constants. At least with requiring static storage duration
on h
, their would be a fallback plan that would still allow the programmer to use this feature. Without even this minimal guarantee, programmers have to fallback on to esoteric workaround code.
Initializer lists are a pure reference type. Shouldn't other standard pure reference types also have similar functionality, such as std::span<const T>
and std::string_view
.
Even these std::span<const T>
and std::string_view
examples would become safe if initializer lists were given a static storage duration
guarantee.
The bonus problem
Another important issue that should be fixed is that the original proposal was for “Static storage for braced initializers” yet the current verbiage is only for initializer lists. If the currently worded functionality is reasonable even for an initializer list of size one, why not for single instance objects that are braced initialized entirely of constant literals.
Might this be viewed as an inconsistency and even worse, a missed opportunity to fix a swath of dangling references by lifetime extending objects to have static storage duration
.
|
|
|
Example #2 Comments
gcc, clang
warning: returning reference to local temporary object [-Wreturn-stack-address]
if not const
error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
arguable should be error regardless of const
C++23 P2266R3 Simpler implicit move returning reference to xvalue is ill formed
|
NOTE: The first and the third would be logically safe had the object had static storage duration
. The second would be caught as an error if compilers such as clang and gcc would correctly implement/deploy Simpler implicit move
. If the bonus solution is accepted, than hopefully future standards could require some local analysis so that the second could be made as safe as the first and third.
The bonus solution
Split the one example …
6.8.7 Temporary objects [class.temporary] Paragraph 6 (6.8)
[Example 3 :
… into two examples, over const.
Further, some verbiage to this effect would be in order.
Impact on the standard
The requested wording changes has the same impacts as stated and accepted in the original feature. C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2
The language impact of the original feature would mirror the impact of requiring the object created in the expression const T& = T;
to have static storage duration. Namely, "Valid C++ 2023 code that relies on the result of pointer comparison between backing arraysobjects may change behavior." C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2
The proposed changes are relative to the current working draft N5014
.
References
Jarrad J. Waterloo <descender76 at gmail dot com>
Evolution Working Group
SG23: Safety and Security
Static storage for braced initializers NBC examples
Table of contents
Abstract
Require
static storage duration
inStatic storage for braced initializers
[1] so that more memory unsafety will not be added to theC++
language and thus ensure that this feature will be utilized instead of being bypassed for legacy code that does offer such assurances.Motivation
The problem
Without
Static storage for braced initializers
[1:1] the programmer has to perform a work around as stated in2.1. Workarounds
section. [2]"This code creates a 2MB backing array on the stack frame of the function that initializes
v
:""This code does not:"
"So the latter is a workaround. But it shouldn’t be necessary to work around this issue; we should fix it instead."
Unfortuntely, with this feature, as currently worded, the programmer still has to do the work around.
9.5.5 List-initialization [dcl.init.list] Paragraph 6
[3] makes no mention ofstatic storage duration
in the specification. It only has an example of what a compiler might do.This is further confirmed in a footnote at the end of the standard.
C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2
[3:1] "Permit the implementation to store backing arrays in static read-only memory."Permit is not a guarantee and consequently is less than the guarantee made in the workaround where the
static
keyword is used.Without a requirement, this feature not only fails to provide the desired performance goals but also fails by increasing inconsistency, ambiguity and unsafety in the language. Consider the following example.
This is actually two examples of potential dangling references,
rilm
andpilm
, being created; both by return and by passthrough. If the compiler choose to make the lifetimes of the backing arrays of the initializer lists to havestatic storage duration
thanrilm
andpilm
are memory safe, race free and thread safe. If the compiler choosesautomatic storage duration
thanrilm
andpilm
are dangling references.If the programmer thinks that their code has
static storage duration
because it was based on the provided example in the standard and the compiler left it asautomatic storage duration
then the programmer likely did not make the necessary code changes to ensure it doesn’t dangle. Not being able to trust this feature will cause programmers to not trust their compilers and to program with the esoteric legacy workaround code, thus defeating the purpose of having this feature in the first place.A programmer can’t be expected to be responsible for dangling, if the programmer can’t definitely reason about the lifetimes of objects. A code reviewer/code auditor can’t be expected to find dangling, if the reviewer/auditor can’t definitely reason about the lifetimes of objects. None of these three people should be forced to look at assembly, a different programming language, to know what the compiler decided to do in this instant and worse to repeat the process for each initializer list/braced initializer in a program.
Without definite verbiage in the standard, this is a non portable language feature between compilers. Worse yet, it may not be portable/stable within a single compiler as the behavior could vary with respect to different compiler flags.
Besides failing to require
static storage duration
the current verbiage fails to even mention the possibility ofstatic storage duration
."The backing array has the same lifetime as any other temporary object (6.8.7), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary."
9.5.5 List-initialization [dcl.init.list] Paragraph 6
[3:2]Except that it is not exactly the same because when the lifetime of a temporary is extended it still has
automatic storage duration
changed from the statement to the block, like a declared variable.C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2 [3:3]
What is proposed is different because it is being extended by being turned into something that has
static storage direction
.The solution
The lifetime of the backing array associated with initializer lists needs to definitely state
static storage duration
in a similar fashion as stated else where in the standard. Consider the very first occurence ofstatic storage duration
in the standard.5.13.5 String literals [lex.string] Paragraph 9 [3:4]
"Evaluating a string-literal results in a string literal object with static storage duration (6.8.6)."
Should a initializer list of ints or even chars be specified radically nebulous from the clear straight forward verbiage of string literals. Fixing the initializer list verbiage with the string literal verbiage would make the two more consistent and in a reasonable fashion.
Even the example provided by the standard could benefit from some revisions.
9.5.5 List-initialization [dcl.init.list] Paragraph 6
[3:5]The current example could be expanded to better illustrate when this feature does kick in versus may kick in.
While it would be ideal if both
h
andi
examples hadstatic storage duration
, sincef({1, x, x, x, 3});
requires analysis of additional lines of code it could make sense, on the short term, that it MAY havestatic storage duration
. Hopefully, even this scenario would be strengthened in futureC++
releases since it is needed for the deduplication of constants. At least with requiringstatic storage duration
onh
, their would be a fallback plan that would still allow the programmer to use this feature. Without even this minimal guarantee, programmers have to fallback on to esoteric workaround code.Initializer lists are a pure reference type. Shouldn't other standard pure reference types also have similar functionality, such as
std::span<const T>
andstd::string_view
.Even these
std::span<const T>
andstd::string_view
examples would become safe if initializer lists were given astatic storage duration
guarantee.The bonus problem
Another important issue that should be fixed is that the original proposal was for “Static storage for braced initializers” yet the current verbiage is only for initializer lists. If the currently worded functionality is reasonable even for an initializer list of size one, why not for single instance objects that are braced initialized entirely of constant literals.
Might this be viewed as an inconsistency and even worse, a missed opportunity to fix a swath of dangling references by lifetime extending objects to have
static storage duration
.Example #2 Comments
gcc, clang
warning: returning reference to local temporary object [-Wreturn-stack-address]
if not const
error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
arguable should be error regardless of
const
C++23
P2266R3Simpler implicit move
returning reference to xvalue is ill formedNOTE: The first and the third would be logically safe had the object had
static storage duration
. The second would be caught as an error if compilers such as clang and gcc would correctly implement/deploySimpler implicit move
[4]. If the bonus solution is accepted, than hopefully future standards could require some local analysis so that the second could be made as safe as the first and third.The bonus solution
Split the one example …
6.8.7 Temporary objects [class.temporary] Paragraph 6 (6.8) [3:6]
[Example 3 :
… into two examples, over const.
Further, some verbiage to this effect would be in order.
Impact on the standard
The requested wording changes has the same impacts as stated and accepted in the original feature. C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2 [3:7]
The language impact of the original feature would mirror the impact of requiring the object created in the expression
const T& = T;
to have static storage duration. Namely, "Valid C++ 2023 code that relies on the result of pointer comparison betweenbacking arraysobjects may change behavior." C.1.4 Clause 9: declarations [diff.cpp23.dcl.dcl] Paragraph 2 [3:8]The proposed changes are relative to the current working draft
N5014
[3:9].References
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2752r3.html ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2752r3.html#workarounds ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5014.pdf ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2266r3.html ↩︎