Explicitly uninitialized variables with = void, with warnings on missing initializers.

114 views
Skip to first unread message

Matthew Fioravante

unread,
Mar 18, 2017, 4:20:37 PM3/18/17
to ISO C++ Standard - Future Proposals
In C++, we inherited this rule from C where primitive types like ints and pointers are uninitialized by default.

Before getting into it, essentially what I'm suggesting is first we add a syntax for explicitly leaving a variable uninitialized

int x = void; //I'm telling you x really is intended to start its life uninitialized
std
::cout << x << std::endl; //undefined behavior

Next, we can suggest compilers warn if you don't provide an initializer for types that would otherwise default to be uninitialized.

int x; //warning no initializer! Use = void to create unintialized.
void* p; //warning no initializer! Use = void to create uninitialized. Suggest = nullptr for pointers.
vector
<int> y; //Ok
int* z = nullptr; //Ok
int w = void; //Ok
char buf[4096] = void; //Ok
struct P { int x = 0; int y = void; int z = void; }; //Ok
P p
= void; //Ok??
std
::vector<int> v = void; //Error, cannot use = void for non-trivial types.

Note that for class types with constructors like vector, you can still go on with no initializer. The warning scheme is what maintains backwards compatibility. Migrating an old codebase to be warning free by just replacing all violations with = void is trivial via tooling nowadays.

There are some real benefits to uninitialized variables and some made up ones. But the bottom line is default uninitialized makes it very easy to write bugs, especially for beginners.

What are the supposed benefits of uninitialized variables?

1. Error detection.

Uninitialized memory access provides louder errors. A UMA can cause a crash with a core dump for ready debugging. They can easily be detected by modern tools like valgrind and address sanitizer. UMA's can also result in non-sense values that are outside of the range of whats expected, more easily corrupting the result of the entire application and making the problem easier to be noticed by an operator.

The last point is also dangerous. Imagine a trading system where an uninitialized memory read causes you to to buy several billion dollars in shares vs an incorrectly initialized variable at 0 simply causes you do not do anything.

2. Optimization opportunities.

This code does 2 copies.

char buf[4096] = {} //initialize to all zeroes
memcpy
(buf, otherbuf, sizeof(buf));

If one removes the `= {}` bit, we don't initialize anything until memcpy is called. Saving useless work.

Some of these are suspicious at best, as modern compiles should be able to detect these scenarios and optimize out the redundant writes. Compilers have been eliminating dead stores for years. Still, we don't always have the best compilers especially in the embedded word. This kind of thing can be checked in the assembly.

In general, being able to take advantage of undefined behavior for error checking and optimization is a good thing. However I don't think its good how easily C++ lets you slide into UB land in subtle and non-obvious ways. Its important to have all of the levers available, they just need to be organized properly so the easy ones are easy and the hard ones are hard.

Another thing really bad about unitialized by default is that it creates an annoying and confusing rule about when you need to initialize variables.

When I write a class with members, like a trained monkey I always remember to explicitly initialize the primitives. Its annoying for me to do this, and more novice programmers often forget to do it.

struct Container {
int x = 0; //I have to remember to do this
Foo* p = nullptr; //Null pointer is almost always better than unitialized, as it ready produces a segfault and easy debugging.
std
::unique_ptr<Foo> p2; //Almost the same as p, but default initialized to null.
std
::vector<int> v;
std
::array<int, 10> a = {{}}; //Yes you really need 2 braces on each side... Why does this have to be so different than std::vector?
};

We can't ever touch the backwards compatible rules about uninitialized variables. What we can do is add warnings and promote a safer more correct style for modern C++. Its still confusing that int x; and vector<int> x; are so different, but at least compilers can protect us from screwing this up until we're expert enough to understand when = void makes sense.

Arthur O'Dwyer

unread,
Mar 19, 2017, 12:13:02 AM3/19/17
to ISO C++ Standard - Future Proposals
On Saturday, March 18, 2017 at 1:20:37 PM UTC-7, Matthew Fioravante wrote:
In C++, we inherited this rule from C where primitive types like ints and pointers are uninitialized by default.

Before getting into it, essentially what I'm suggesting is first we add a syntax for explicitly leaving a variable uninitialized

int x = void; //I'm telling you x really is intended to start its life uninitialized
std
::cout << x << std::endl; //undefined behavior

Next, we can suggest compilers warn if you don't provide an initializer for types that would otherwise default to be uninitialized.

Note that for class types with constructors like vector, you can still go on with no initializer. The warning scheme is what maintains backwards compatibility.

What you're describing sounds good, but you've picked the wrong syntax for it. Look at the semantics again:
- We want to add a semi-standardized diagnostic for an existing construct
- We need a way to annotate the construct in order to suppress the diagnostic
- The annotation should have no effect on codegen

What kind of syntax does C++ already have for this kind of thing? Answer: attributes.
In fact what you're proposing is basically identical to the existing C++17 attribute [[maybe_unused]], in terms of its interactions with codegen and diagnostics.

    int x [[uninitialized]];  // yes, compiler, I know this variable is uninitialized; please don't complain about it

    struct Foo {
        int m [[uninitialized]];  // yes, compiler, I know this member isn't always initialized; please don't complain about it
        Foo() { }
    };

A proposal along these lines would sound good to me.
Sadly I'm not sure there's any prior art for actually giving such warnings. Maybe in compilers that support MISRA-C++...?

–Arthur

Matthew Fioravante

unread,
Mar 19, 2017, 1:55:47 AM3/19/17
to ISO C++ Standard - Future Proposals


On Saturday, March 18, 2017 at 11:13:02 PM UTC-5, Arthur O'Dwyer wrote:

What you're describing sounds good, but you've picked the wrong syntax for it. Look at the semantics again:
- We want to add a semi-standardized diagnostic for an existing construct
- We need a way to annotate the construct in order to suppress the diagnostic
- The annotation should have no effect on codegen

What kind of syntax does C++ already have for this kind of thing? Answer: attributes.

I agree that attributes were designed for this thing but that doesn't mean you have to use an attribute. While its true = void actually does nothing for codegen, its much shorter and less verbose and ugly than an attribute.

While the final syntax can be debated, I much prefer to write this:

int x = void;

 Instead of this

int x [[uninitialized]];

The first one is natural and pretty obvious in that it says "I'm assigning nothing to x". The second one while readable is ugly and long.

Implementing warnings is a good way to get past some of the backward compatible pitfalls in C++. However attribute syntax is ugly and verbose, and I'm worried C++ of the future will be full of attributes everywhere to suppress warnings.

People have also moaned and groaned about how things like std::vector<int> will always zero intiailize the elements. We could think about extending this = void concept to allowing one to create a vector<int> that starts uninitialized. Then the syntax actually does affect code gen in some cases and is consistent wherever its used.

Bo Persson

unread,
Mar 19, 2017, 6:20:57 AM3/19/17
to std-pr...@isocpp.org
On 2017-03-19 06:55, Matthew Fioravante wrote:
>
>
> On Saturday, March 18, 2017 at 11:13:02 PM UTC-5, Arthur O'Dwyer wrote:
>
>
> What you're describing sounds good, but you've picked the wrong
> syntax for it. Look at the semantics again:
> - We want to add a semi-standardized diagnostic for an existing
> construct
> - We need a way to annotate the construct in order to suppress the
> diagnostic
> - The annotation should have no effect on codegen
>
> What kind of syntax does C++ already have for this kind of thing?
> Answer: attributes.
>
>
> I agree that attributes were designed for this thing but that doesn't
> mean you have to use an attribute. While its true = void actually does
> nothing for codegen, its much shorter and less verbose and ugly than an
> attribute.
>
> While the final syntax can be debated, I much prefer to write this:
>
> |
> int x =void;
> |
>
> Instead of this
>
> |
> int x [[uninitialized]];
> |
>
> The first one is natural and pretty obvious in that it says "I'm
> assigning nothing to x". The second one while readable is ugly and long.

What's wrong with being ugly and verbose when doing unusual things? It's
not that the code is just full of uninitialized variables, right.

We already have things like reinterpret_cast that is ugly by design, in
part to make the programmer think about if this *really* is needed:
"What will the other guys say at the code review?"



Bo Persson


Jonathan Müller

unread,
Mar 19, 2017, 8:36:28 AM3/19/17
to std-pr...@isocpp.org, Ville Voutilainen
On 19.03.2017 05:13, Arthur O'Dwyer wrote:
>
> What kind of syntax does C++ already have for this kind of thing?
> Answer: attributes.
> In fact what you're proposing is basically identical to the existing
> C++17 attribute [[maybe_unused]], in terms of its interactions with
> codegen and diagnostics.
>

I've written a proposal for exactly that a while back:
http://foonathan.net/attribute-uninitialized-proposal.html

Ville, I've emailed it to you as well, as you've requested, what's the
status on that?

Ville Voutilainen

unread,
Mar 19, 2017, 8:52:55 AM3/19/17
to Jonathan Müller, ISO C++ Standard - Future Proposals
On 19 March 2017 at 14:36, Jonathan Müller
As noted in the email thread on it, we'll include it for the
discussion in Toronto, thanks for the reminder.
Material like that wasn't much looked at in Kona because we had bigger
fish to fry.
Reply all
Reply to author
Forward
0 new messages