C#
memory address
object reference
programming
pointers

Memory address of an object in C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Despite the title, this is really a C# question, not a C question. In C#, objects live in managed memory, so you normally do not work with stable raw memory addresses the way you would in C or C++. If you truly need an address, you must usually pin the object first so the garbage collector cannot move it.

Why C# Hides Raw Addresses

In ordinary C# code, a variable of reference type holds a managed reference, not a pointer value you are supposed to manipulate directly. The CLR and garbage collector decide where the object lives and may relocate it during collection.

That means two important things follow:

  • the language does not treat object addresses as everyday programming tools,
  • and any address you obtain is only meaningful while the object remains pinned or otherwise fixed in memory.

So the first answer is architectural: most C# code should not care about object addresses at all.

Getting the Address of a Value in Unsafe Code

For unmanaged value data, C# can expose an address inside an unsafe block.

csharp
1using System;
2
3public class Program
4{
5    public static unsafe void Main()
6    {
7        int number = 42;
8        int* ptr = &number;
9
10        Console.WriteLine((nint)ptr);
11        Console.WriteLine(*ptr);
12    }
13}

This is the closest to C-style address handling, but it works naturally only for unmanaged values. It also requires compiling with unsafe code enabled.

Getting the Address of Managed Data With fixed

If you need the address of managed array data or a fixed buffer during interop, use the fixed statement:

csharp
1using System;
2
3public class Program
4{
5    public static unsafe void Main()
6    {
7        byte[] buffer = { 1, 2, 3, 4 };
8
9        fixed (byte* ptr = buffer)
10        {
11            Console.WriteLine((nint)ptr);
12            Console.WriteLine(ptr[0]);
13        }
14    }
15}

Inside the fixed block, the garbage collector will not move that pinned object, so the pointer stays valid for the duration of the block.

This is the normal pattern for interop code that must pass a memory address to native APIs.

Pinning a Reference Type With GCHandle

For a managed object, one common way to pin it is with GCHandle:

csharp
1using System;
2using System.Runtime.InteropServices;
3
4public class Payload
5{
6    public int Value;
7}
8
9public class Program
10{
11    public static void Main()
12    {
13        byte[] data = { 10, 20, 30, 40 };
14        var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
15
16        try
17        {
18            IntPtr address = handle.AddrOfPinnedObject();
19            Console.WriteLine(address);
20        }
21        finally
22        {
23            handle.Free();
24        }
25    }
26}

This is useful when native code needs a pointer. It is not something you should do casually in normal application logic.

Why You Usually Should Not Do This

Pinning objects prevents the garbage collector from moving them, which can hurt memory management if done too often or for too long. Raw addresses also become brittle because they are meaningful only within the constraints of the runtime and the pinning window.

Most higher-level C# tasks should use:

  • references,
  • spans,
  • 'Memory<T>,'
  • or marshaling APIs,

instead of treating addresses as application-level data.

object.GetHashCode() Is Not an Address

One common beginner mistake is to treat GetHashCode() as if it were a memory address. It is not. A hash code is just an integer used for hashing behavior and may change across runs or differ from object identity entirely.

Likewise, printing a reference type or calling RuntimeHelpers.GetHashCode does not give you a real object address.

Interop Is the Main Legitimate Use Case

The most common reason to obtain an address in C# is interoperability with unmanaged code. For example, a native library may expect a pointer to a buffer.

In those cases, the right question is usually not "how do I inspect the address for curiosity," but:

  • how do I pin the data safely,
  • how long must it stay pinned,
  • and what marshaling pattern best fits the API.

That framing leads to safer code.

Common Pitfalls

The biggest pitfall is assuming a managed object has one stable address for its whole lifetime. The garbage collector may move it unless you pin it.

Another mistake is using object hashes or debugger displays as if they were real addresses. They are not.

Developers also often reach for unsafe pointers when Span<T>, Memory<T>, or normal references would solve the problem with less risk.

Finally, pinning too much memory for too long can hurt garbage collector performance. Keep pinning scopes short and deliberate.

Summary

  • In C#, raw object addresses are not normal application-level concepts.
  • Managed objects can move in memory unless they are pinned.
  • Use unsafe and fixed for short-lived pointer access when needed.
  • Use GCHandle when interop requires a pinned managed object.
  • Most C# code should work with references and safe abstractions rather than raw addresses.

Course illustration
Course illustration

All Rights Reserved.