Are C# Uninitialized Variables Dangerous

Are C# uninitialized variables dangerous?

I am under the impression that there are not truly "unassigned" values allowed by the runtime. In particular that a reference type that is not initialized will always have a null value, never the value left over from a previous invocation of the method or random value. Is this correct?

I note that no one has actually answered your question yet.

The answer to the question you actually asked is "sorta".

As others have noted, some variables (array elements, fields, and so on) are classified as being automatically "initially assigned" to their default value (which is null for reference types, zero for numeric types, false for bools, and the natural recursion for user-defined structs).

Some variables are not classified as initially assigned; local variables in particular are not initially assigned. They must be classified by the compiler as "definitely assigned" at all points where their values are used.

Your question then is actually "is a local variable that is classified as not definitely assigned actually initially assigned the same way that a field would be?" And the answer to that question is yes, in practice, the runtime initially assigns all locals.

This has several nice properties. First, you can observe them in the debugger to be in their default state before their first assignment. Second, there is no chance that the garbage collector will be tricked into dereferencing a bad pointer just because there was garbage left on the stack that is now being treated as a managed reference. And so on.

The runtime is permitted to leave the initial state of locals as whatever garbage happened to be there if it can do so safely. But as an implementation detail, it does not ever choose to do so. It zeros out the memory for a local variable aggressively.

The reason then for the rule that locals must be definitely assigned before they are used is not to prevent you from observing the garbage uninitialized state of the local. That is already unobservable because the CLR aggressively clears locals to their default values, the same as it does for fields and array elements. The reason this is illegal in C# is because using an unassigned local has high likelihood of being a bug. We simply make it illegal, and then the compiler prevents you from ever having such a bug.

Is it true that if the address of an uninitialized variable is taken, it is initialized to default value?

In the unsafe code, things may become strange.

First of all, let's look at the Variables section of the C# language specification.

A variable must be definitely assigned <...> before its value can be obtained.

<...> variables are either initially assigned or initially unassigned. <...> An initially unassigned variable has no initial value. For an initially unassigned variable to be considered definitely assigned at a certain location, an assignment to the variable must occur in every possible execution path leading to that location.

That is true for the "safe" C# code.

In the unsafe code specification, there is an address-of operator description, which tells us:

The & operator does not require its argument to be definitely assigned, but following an & operation, the variable to which the operator is applied is considered definitely assigned in the execution path in which the operation occurs. It is the responsibility of the programmer to ensure that correct initialization of the variable actually does take place in this situation.

This is exactly your case. You apply the address-of operator to an initially unassigned variable, and after that it is considered by the compiler as a definitely assigned variable - so you can use it even in the "safe" context.

The specification tells us that the programmer is responsible for the initialization of the variable. If no initialization occurs, the specification does not guarantee what value will be stored in the actual variable.

The current C# compiler produces such IL code for this:

.locals init (
[0] int32 a
)

IL_0000: ldloca.s 0
IL_0002: conv.u
IL_0003: call void Namespace.Program::f(void*)

You see, there is a single local variable of a value type int32.
The current CLR (.NET Framework) auto-initializes all local variables with zeroes - that's why the initial value of your variable will be 0 even though it is not initialized in code.

I wouldn't rely on this fact - AFAIK it's not guaranteed and might change in other implementations.

Why is my variable still uninitialized ?

Some background from the C# specification (5.3.3.14):

For a try statement stmt of the form:

try try-block finally finally-block

(...)

The definite assignment state of v at the beginning of finally-block
is the same as the definite assignment state of v at the beginning of
stmt.

Edit Try-Catch-Finally(5.3.3.15):

Definite assignment analysis for a try-catch-finally statement (...)
is done as if the statement were a try-finally statement enclosing a
try-catch statement

The following example demonstrates how the different blocks of a try
statement (§8.10) affect definite assignment.

class A
{
static void F()
{
int i, j;
try {
goto LABEL;
// neither i nor j definitely assigned
i = 1;
// i definitely assigned
}
catch {
// neither i nor j definitely assigned
i = 3;
// i definitely assigned
}
finally {
// neither i nor j definitely assigned
j = 5;
// j definitely assigned
}
// i and j definitely assigned
LABEL:;
// j definitely assigned
}
}

I just thought of an example that shows the problem better:

int i;
try
{
i = int.Parse("a");
}
catch
{
i = int.Parse("b");
}
finally
{
Console.Write(i);
}

What is the size of uninitialized struct vs uninitialized class in c#?

I know that if this member variable is of a class type, the size of the member variable will be the size of an int when it is initialized as it will just reference an object instance.

No you do not know that because it is false.

References are 32 bits in a 32 bit process and 64 bits in a 64 bit process. That's why we call them 64 bit processes.

A reference is only the size of a 32 bit int in a 32 bit process.

What is the size of an uninitialized member variable here?

First of all, there's no such thing as an uninitialized member variable. All member variables are initialized to their default values by the memory allocator. There are unassigned member variables, but there are no uninitialized member variables in C#. An unassigned member variable has the value it was initialized with: it's default value.

Second, the size of a variable does not change when it is assigned. Member variables of reference type take up as much storage as one reference whether they are assigned or not.

What is the size of this member variable if it is a struct type?

That is implementation defined. It depends on details such as how the structure members are packed, and whether there are any layout attributes.

My guess is that the size is sizeof('struct_type')

sizeof(T) for user-defined-type T only works in unsafe code, and only when the struct is all "unmanaged" types, like integers, pointer types, and so on.

But when it does work, yes, you can rely on it to tell you how big a thing is in memory.

Note that the marshaller's SizeOf tells you how big a thing is when it is being copied across a marshalling boundary, not how big it is in memory.

In this example, would StructParent be roughly 5 times the size of ClassParent?

No. You're forgetting that all object that are instances of reference type (including boxed value types) have an object header. The object header is the size of two references.

On a 32 bit machine we would expect an instance of StructParent to take up 20 bytes for its fields, and 8 bytes for its header, for a total of 28. An instance of ClassParent takes up 8 bytes for the header and 4 bytes for the member variable for a total of 12, which is not one fifth of 28.

Why does this if statement not intialize or update my variable?

If the compiler can't determine that result is initialized before using, it will fail with error CS0165 (Use of unassigned local variable).

Considering that num1 is not a constant, compiler can't guarantee that it stays 1 after the initialization and result = mu,1 + num2 will be executed. This example might look trivial, but it won't be difficult to change the code in a way that another thread can manipulate the values between the initialization of num1 and the check. But when you change the condition to true, then the compiler can determine that result will always be initialized prior to the usage, hence it compiling.

Similarly, when result is initialized to zero at declaration, then again compiler has a guarantee that it is initialized prior to usage.

How to avoid initialization of these variable?

You've misread his article. In his article he is specifically talking about initialization of variables with respect to classes. In the case you've put forth, your variables should be initialized before they can be used because they'll be immediately used.

Edit: Yes, in this specific case the int variables don't need initialization because the compiler automatically initializes an int to 0, but if this is taken to a different degree with a string or a DateTime, initialization becomes important in the context of a method.

Is there any way to make C# not complain about uninitialized variables that are actually not uninitialized?

This is the usual workaround:

List<U> details = null;

Why do I have to assign a value to the variables before I do TryParse?

The compiler just isn't smart enough.

Something like this is totally OK:

double a, b;
if (double.TryParse("5", out a) && double.TryParse("6", out b))
// works fine
Console.WriteLine("a = {0}, b = {1}", a, b);

But if we make the conditional statement a little more complex, then suddenly the compiler is no longer sure whether you've initialized the variable or not:

double a, b;
if (true == (double.TryParse("5", out a) && double.TryParse("6", out b)))
// generates an error
Console.WriteLine("a = {0}, b = {1}", a, b);

It's perfectly fine to initialize those variables to 0.

Why C# local variable should be assigned directly, even if it's default value?

basically - this is what MS decided.

If you want more you can read here and check Eric Lippert’s Blog

The reason this is illegal in C# is because using an unassigned local has high likelihood of being a bug.



Related Topics



Leave a reply



Submit