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?
An uninitialized variable has an undefined value, often corresponding to the data that was already in the particular memory location that the variable is using. This can lead to errors that are very hard to detect since the variable's value is effectively random, different values cause different errors or none at all.
So using an uninitialized variable will result in undefined behavior. Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.
In computing, an uninitialized variable is a variable that is declared but is not set to a definite known value before it is used. It will have some value, but not a predictable one. As such, it is a programming error and a common source of bugs in software.
An uninitialized variable is a variable that has not been given a value by the program (generally through initialization or assignment). Using the value stored in an uninitialized variable will result in undefined behavior.
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.
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With