Wird geladen...

Unsicherer Code und Zeiger in C#

Lernen Sie unsicheren Code und Zeiger in C#, um mit Speicheradressen und Low-Level-Operationen zu arbeiten.

Obwohl die C#-Sprache über eine sichere Speicherverwaltung verfügt, kann in bestimmten Hochleistungs­szenarien ein direkter Zugriff auf den Speicher erforderlich sein. Unsafe Code wird für solche Fälle verwendet. Unsichere Blöcke (unsafe) ermöglichen den direkten Zugriff auf Speicheradressen mithilfe von Zeigern (pointers). Dieser Ansatz bietet eine niedrige Kontrollebene, muss jedoch mit Vorsicht eingesetzt werden, da er außerhalb des Schutzes des .NET-Garbage Collectors (GC) arbeitet.


Was ist Unsicherer Code (Unsafe Code)?

Das Schlüsselwort unsafe teilt dem C#-Compiler mit, dass der Code mit nicht verwaltetem Speicher arbeitet. In solchem Code führt der Compiler keine Speicher­sicherheitsprüfungen durch. Um unsicheren Code zu verwenden, muss in den Projekteinstellungen die Option „Allow unsafe code“ aktiviert werden.


// Eigenschaft, die zur .csproj-Datei hinzugefügt werden muss:
<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Was ist ein Pointer?

Ein Pointer ist eine Variable, die die Speicheradresse einer anderen Variable speichert. In C# dürfen Pointer nur innerhalb von unsafe-Blöcken deklariert und im Stack verwendet werden. Das Zeichen * deklariert einen Pointer, während der Operator & die Adresse einer Variablen abruft.


using System;

class Program
{
    static unsafe void Main()
    {
        int x = 42;
        int* ptr = &x; // Adresse von x erhalten

        Console.WriteLine($"Wert von x: {*ptr}");
        Console.WriteLine($"Adresse von x: {(ulong)ptr}");
    }
}

Der Ausdruck *ptr bedeutet „Lese den Wert an der referenzierten Adresse“. In diesem Beispiel wird sowohl auf die Adresse als auch auf den Wert der Variablen zugegriffen.


Werte über Pointer ändern

Mit einem Pointer kann der Wert einer Variablen direkt geändert werden. Dies funktioniert genauso wie in C oder C++.


unsafe
{
    int x = 10;
    int* p = &x;

    *p = 99; // Wert von x über Pointer ändern
    Console.WriteLine($"Neuer Wert von x: {x}");
}

// Ausgabe:
// Neuer Wert von x: 99

Zeigerarithmetik (Pointer Arithmetic)

Mit der Zeigerarithmetik kann man durch Arrays navigieren, indem man Adressen erhöht. Dies ist jedoch nur bei primitiven Datentypen wie int, byte oder double erlaubt.


unsafe
{
    int[] zahlen = { 10, 20, 30, 40 };
    fixed (int* p = zahlen)
    {
        for (int i = 0; i < zahlen.Length; i++)
            Console.WriteLine($"Adresse: {(ulong)(p + i)}, Wert: {*(p + i)}");
    }
}

Das Schlüsselwort fixed fixiert das Array im Speicher, sodass der GC es nicht verschieben kann. Dadurch zeigt der Pointer immer auf die korrekte Adresse.


Das Schlüsselwort fixed

fixed verhindert, dass ein verwaltetes Objekt (z. B. Array oder String) vom GC verschoben wird. Es ermöglicht den sicheren Zugriff auf Speicheradressen über Pointer.


unsafe
{
    string text = "Hallo";
    fixed (char* p = text)
    {
        for (int i = 0; i < text.Length; i++)
            Console.Write(p[i]);
    }
}

Hinweis: Nach dem Ende des fixed-Blocks kann das Objekt wieder verschoben werden. Die Verwendung des Pointers außerhalb dieses Blocks ist daher unsicher.


Speicherzugriff mit struct und Pointer

Mit Pointern kann auch direkt auf Speicher in structs zugegriffen werden. Dies wird häufig in performancekritischen Szenarien verwendet (z. B. Spiele-Engines, Grafikberechnung, Native Interop).


using System;

struct Punkt
{
    public int X;
    public int Y;
}

class Program
{
    static unsafe void Main()
    {
        Punkt p = new Punkt { X = 5, Y = 10 };
        Punkt* ptr = &p;

        ptr->X = 20;
        Console.WriteLine($"Punkt: X={ptr->X}, Y={ptr->Y}");
    }
}

Der Ausdruck ptr->X entspricht der Strukturzugriffs­syntax in C/C++.


Speicher auf dem Stack mit stackalloc reservieren

stackalloc reserviert direkt rohen Speicher auf dem Stack. Dieser Speicher wird automatisch am Ende des Gültigkeitsbereichs freigegeben.


unsafe
{
    int* p = stackalloc int[5];
    for (int i = 0; i < 5; i++)
        p[i] = i * 2;

    for (int i = 0; i < 5; i++)
        Console.WriteLine(p[i]);
}

stackalloc ist schnell, sollte aber nur für kleine Datenmengen verwendet werden. Für größere Datenmengen sollte Speicher im Heap (new) zugewiesen werden.


Wann sollte Unsicherer Code verwendet werden?


Leistungsvergleich

Unsicherer Code mit direktem Speicherzugriff kann durch das Umgehen des GC eine Leistungssteigerung von 30–50 % bieten. Dies geschieht jedoch auf Kosten eines höheren Fehlerrisikos. Ein falscher Speicherzugriff kann zu Abstürzen oder Speicherbeschädigungen führen.


Wichtige Hinweise


Beispiel: Zugriff auf Bildpixel-Daten

Das folgende Beispiel zeigt, wie man mit einem unsafe-Block direkt auf Pixel in einem Bitmap zugreifen kann. Diese Methode führt Pixeloperationen (z. B. Farb­umkehr) deutlich schneller aus als herkömmliche APIs.


using System;
using System.Drawing;
using System.Drawing.Imaging;

class Program
{
    static unsafe void Main()
    {
        using Bitmap bmp = new Bitmap("image.png");

        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

        int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
        byte* firstPixel = (byte*)data.Scan0;

        for (int y = 0; y < bmp.Height; y++)
        {
            byte* row = firstPixel + (y * data.Stride);
            for (int x = 0; x < bmp.Width; x++)
            {
                byte* pixel = row + (x * bytesPerPixel);
                pixel[0] = (byte)(255 - pixel[0]); // Blaukanal invertieren
                pixel[1] = (byte)(255 - pixel[1]); // Grünkanal invertieren
                pixel[2] = (byte)(255 - pixel[2]); // Rotkanal invertieren
            }
        }

        bmp.UnlockBits(data);
        bmp.Save("output.png");
        Console.WriteLine("Das Bild wurde farblich invertiert!");
    }
}

Dieses Beispiel zeigt die Leistungsfähigkeit von unsicherem Code bei Hochleistungs-Pixel-Manipulation.


TL;DR

  • unsafe-Blöcke ermöglichen in C# den direkten Speicherzugriff über Pointer.
  • Das Schlüsselwort fixed verhindert, dass der GC Objekte im Speicher verschiebt.
  • stackalloc ermöglicht eine schnelle, temporäre Speicherzuweisung auf dem Stack.
  • Bietet Leistungsgewinne, birgt jedoch erhebliche Sicherheitsrisiken.
  • Wird häufig für Interop mit nicht verwaltetem (C/C++)-Code oder Speicherzugriffe auf niedriger Ebene verwendet.

Ähnliche Artikel