CyberMarmot
CyberMarmot

Reputation: 61

Connection between uninitialized variables and type safety

I would like to ask why using variables that are not initialized is considered non type-safe?

I'm reading Bjarne Stroustrup's beginner book(Programming Principles and Practice Using C++) from the C++ book guide on this site.

There is a part in the book about type-safety that states :

A program - or a part of a program - is type-safe when objects are used only according to the rules for their type. For example, using a variable before it has been initialized is not considered type-safe.

Then the book provides the following code as an example:

 int main() {
        double x; // we "forgot" to initialize
                  // the value of x is undefined

        double y = x; // the value of y is undefined
        double z = 2.0+x; // the meaning of + and the value of z are undefined
 }

I understand that a local variable that is not initialized will have an indeterminate value and reading this variable will cause undefined behavior. What I do not understand is how is it connected to type-safety. We still know the types from the variable's definition.

Why does the comment in the above code states that the meaning of + is undefined when both 2.0 and x are double, and the + is defined for double + double?

Upvotes: 5

Views: 1098

Answers (3)

MikeTheCoder
MikeTheCoder

Reputation: 151

@codekaizer and @Shankar are right: undefined behavior is, by definition, not type safe behavior. How that applies to primitive types is a little harder to wrap your head around, though. It seems reasonable that any appropriately long sequence of bits could be a valid int. As @BoPersson pointed out below, this is not strictly true and implementations are free to include values which cause interrupts under arithmetic. For integers this practically only applies to 0 when used to divide, but that does not mean the standard doesn't allow for an integer version of something like floating point NaN on a suitably unusual architecture.

The reader may find an example with virtual functions more intuitively illustrative of why uninitialized variables aren't type safe. Consider:

struct Base {
    virtual int foo() const =0;
};

struct DerivedA : public Base {
    int foo() const override { return 10; }
};

struct DerivedB : public Base {
    int foo() const override { return -10; }
};

int main() {
    Base* abstractStructPtr;
    std::cout << abstractStructPtr->foo() << std::endl;
    return 0;
}

The type of abstractStructPtr means you can call foo() on it. The expression is valid: abstractStructPtr has a type, that is why you can call foo(). However, the implementation of foo() lives in derived classes.

Since abstractStructPtr isn't initialized, the data it points to isn't guaranteed to be structured in such a way that it can fulfill the call to foo(). In other words, while the type of absractStructPtr is Base*, there is no guarantee that the data that is pointed to is actually a Base object of any kind. Calling foo() is thus undefined behavior and not type safe. Anything could happen; practically it will probably just crash via a memory access violation, but it might not! Kablooey.

Upvotes: 2

Joseph D.
Joseph D.

Reputation: 12174

Undefined behavior means the output could be what you expect or some indeterminate value that may be outside the valid range of a type.

One clear example of undefined behavior is signed integer overflow:

unsigned int i;   // uninitialized
int x = i + 2;    // indeterminate value
if (x + 1 > x) {} // undefined behavior due to signed overflow

x can have a value outside int valid range if i holds a max value of unsigned int.

Thus, type safety is not guaranteed for expressions having indeterminate values.

Upvotes: 2

user5920427
user5920427

Reputation:

Even though 'x' was declared double, since it was not initialized, it has a random bit pattern in memory and that pattern might not represent any valid double precision number. Hence the "meaning of z" is undefined.

Upvotes: 0

Related Questions