ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Follow publication

Where are Objects Allocated in C#? Understanding Heap and Stack

--

In the world of C# programming, understanding where objects are allocated is crucial for optimizing memory usage and ensuring efficient code execution. In this comprehensive guide, we will delve into the two primary memory locations for object allocation in C#: the heap and the stack. By grasping these concepts, you’ll be equipped with valuable insights that can help you write high-performance C# applications.

Heap vs. Stack: Unveiling the Allocation Paradigm

In C#, objects can be stored either on the heap or the stack, and comprehending the differences between these two memory locations is essential.

  • Heap: The heap is a vast memory area where objects are allocated dynamically. It provides flexibility, as objects stored here can be accessed from anywhere within the application. Reference types, such as classes, interfaces, and delegates, are always allocated on the heap. When passing a reference object as a parameter or assigning it to a variable, you are actually passing its reference, which can be allocated on either the stack or the heap.
  • Stack: The stack, on the other hand, is a relatively small and fast memory region that stores method-specific data. Objects allocated on the stack are limited to the scope of a stack frame, meaning they are only accessible within the execution of a specific method.

Allocating Objects: Reference Types and Value Types

Now that we understand the basics of heap and stack memory, let’s explore how different types of objects are allocated in C#.

  • Reference Types: Reference types, including classes, interfaces, and delegates, are always allocated on the heap. When passing a reference object as a parameter or assigning it to a variable, you are passing the reference to its location on the heap. This allows your code to access the object and manipulate it as needed. It’s important to note that the reference itself, not the referenced object, can be allocated on either the stack or the heap.
  • Value Types: Value types, derived from System.ValueType (e.g., int, bool, char, enum, and structs), can be allocated on either the heap or the stack, depending on their declaration. Here’s a breakdown:
  • If a value type is declared as a variable within a method, it is stored on the stack. The “surfaceArea” as an example.
private double CalculateCircleSurfaceArea(double radius)
{
double surfaceArea = Math.PI * radius * radius;

return surfaceArea;
}
  • If a value type is declared as a method parameter, it is stored on the stack. The “radius” in above code as an example.
  • If a value type is declared as a member of a class, it is stored on the heap along with its parent. The “Radius” as an example.
class Circle
{
public double Radius { get; set; } // Value type (double) declared as a member of the Circle class

public double CalculateSurfaceArea()
{
double surfaceArea = Math.PI * Radius * Radius;

return surfaceArea;
}
}
  • If a value type is declared as a member of a struct, it is stored wherever that struct is stored.

struct Rectangle
{
public int Width; // Value type (int) declared as a member of the struct
public int Height;

public int CalculateArea()
{
return Width * Height;
}
}

Starting with C# 7.2, you can declare a struct as a ref struct, ensuring it is always allocated on the stack and preventing it from being declared inside reference types.


ref struct Point
{
public int X;
public int Y;
}

You can check more about ref structure here.

Reference Semantics for Value Types

While value types are typically passed by copy, C# provides mechanisms to allow accessing value types by reference, thus introducing reference semantics. Let’s explore some of the keywords and constructs that enable this:

  • ref and out: The keywords ref and out allow passing value types by reference, meaning the consuming code receives a reference to the value instead of a copy. This applies to both stack and heap allocations, as long as the lifetime of the value type exceeds that of the consuming code.
  • ref return and ref local (C# 7.0): These constructs enable returning a reference to a value type or storing a reference to a value type in a local variable, respectively. They provide convenient ways to work with value types by reference.
using System;

class Program
{
static void Main()
{
int[] numbers = { 1, 2, 3, 4, 5 };

ref int target = ref FindValue(3, numbers); // Using ref returns to get a reference to the target value

target *= 10; // Modifying the target value using the reference

Console.WriteLine("Modified Value: " + target);
Console.WriteLine("Updated Array: " + string.Join(", ", numbers));

// Output:
// Modified Value: 30
// Updated Array: 1, 2, 30, 4, 5
}

static ref int FindValue(int value, int[] array)
{
for (int i = 0; i < array.Length; i++)
{
if (array[i] == value)
{
return ref array[i]; // Using ref locals to return a reference to the matching element
}
}

throw new ArgumentException("Value not found in the array.");
}
}
  • in (C# 7.2): The in keyword allows passing value types by read-only reference, ensuring the value remains unchanged within the consuming code. This avoids unnecessary copying and improves performance.

Memory Management: Garbage Collection and the Heap

One of the critical aspects of managing memory in C# is understanding how the heap memory is freed up. Unlike objects on the stack that are automatically deallocated when the corresponding stack frame is popped, objects on the heap require the intervention of the garbage collector.

When an object on the heap no longer has any references pointing to it, it becomes eligible for garbage collection. At a certain point, the garbage collector kicks in, interrupts running threads, invokes the finalizers of the objects it needs to collect (on a special finalizer thread), and then marks the memory as available for reuse.

The Large Object Heap (LOH): A Special Memory Space

In C#, the heap is further divided into the Small Object Heap (SOH) and the Large Object Heap (LOH). This separation is primarily to address memory fragmentation and optimize memory usage.

  • Small Object Heap (SOH): Objects smaller than 85 kilobytes (KB) are stored on the SOH. The 85KB threshold was determined empirically as the point beyond which defragmentation no longer provides significant performance benefits.
  • Large Object Heap (LOH): Objects larger than or equal to 85KB are stored on the LOH. However, arrays of double are an exception and are stored on the LOH only if they contain more than 1000 elements. The LOH memory space is generally not defragmented, which ensures better performance at the cost of slightly less efficient memory usage.

Understanding the Small Object Heap (SOH) and the Large Object Heap (LOH) is vital for memory optimization in your C# applications, as it allows you to make informed decisions regarding object size and memory allocation.

Sign up to discover human stories that deepen your understanding of the world.

--

--

Published in ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Written by Gevorg Chobanyan

🚀 C# | Unity Game Developer | Crafting immersive experiences, pushing boundaries, and turning dreams into interactive realities. Let's level up together! 🎮✨

No responses yet

Write a response