Memory Allocation Fundamentals
In computer systems, memory is organized as a sequence of addressable bytes, where each byte consists of 8 bits. When a variable is declared, the runtime reserves a contiguous block of memory starting at a specific address. The size of this block is determined by the variable's data type.
Value Type Storage
Value types (such as primitives and structs) store their data directly in the memory allocated on the stack. The data format depends on the specific type. For instance, a short occupies 16 bits (2 bytes).
Example: Binary Representation
Consider the following declaration:
short sensorVal = -2048;
To store this negative number, the system uses Two's Complement representation:
- Convert the absolute value 2048 to binary:
0000 1000 0000 0000. - Invert the bits:
1111 0111 1111 1111. - Add 1 to the result:
1111 1000 0000 0000.
This final 16-bit sequence is stored directly in the stack memory allocated for sensorVal.
Reference Type Storage
Reference types operate differently. When a reference type variable is declared, memory is allocated on the stack to hold a reference (often a memory address). Initially, if not initialized, this reference is null (conceptually zero). The actual instance data is stored on the Managed Heap. The stack variable merely holds the address pointing to this heap location.
Variable Initialization and Defaults
C# enforces strict rules regarding initialization to ansure type safety.
- Member Variables: Fields within a class or struct are automatically initialized to their default values by the runtime if no explicit value is provided. For numeric value types, this default is
0. - Local Variables: Variables declared inside a method body are not automatically initialized. The compiler enforces that a local variable must be assigned a value before it is read; failure to do so results in a compilation error.
Constants
The const keyword designates a variable as a compile-time constant. Once declared, a constant's value is immutable and must be assigned at the point of declaration.
const int MaxRetries = 5;
Boxing and Unboxing
Boxing and unboxing are processes that allow value types to be treated as reference types (specifically object). While useful for polymorphism, they introduce performance overhead.
Boxing
Boxing occurs when a value type is assigned to a variable of type object.
- The runtime allocates memory on the heap to contain the value type.
- The value of the value type is copied into this new heap memory.
- The object reference on the stack is updated to point to the address of the new heap object.
This process involves allocation and copying, which incurs a performance cost.
Unboxing
Unboxing is the reverse operation: converting the reference type back into a value type.
- The runtime checks the object instance on the heap to ensure it is a boxed value of the correct value type.
- The value contained in the heap object is copied into the stack-based value type variable.
Code Example:
static void Main(string[] args)
{
int accountId = 1001;
// Boxing: Value is copied from stack to heap
object objRef = accountId;
// Unboxing: Value is verified and copied from heap to stack
int restoredId = (int)objRef;
}