Code unsafe
en C#
1. Pourquoi le code unsafe
existe-t-il ?
Le mot-clé unsafe
en C# permet de manipuler directement la mémoire à l’aide de pointeurs, ce qui est généralement interdit en programmation managée. Il existe pour :
- Optimiser les performances : Accès direct à la mémoire peut être plus rapide dans certains cas.
- Interopérabilité : Facilite l’intégration avec du code natif en C/C++.
- Manipuler des structures mémoire spécifiques : Utile pour le traitement bas-niveau, comme les moteurs de jeu ou certaines applications système.
Cependant, l’utilisation du code unsafe
désactive certaines protections offertes par le garbage collector ou le compilateur et peut entraîner des erreurs mémoire difficiles à déboguer.
2. Fonctionnalités disponibles
Opérateurs et instructions disponibles
Opérateur/Instruction | Utilisation |
---|---|
* |
Déférencement d’un pointeur. |
-> |
Accès à un membre d’une structure via un pointeur. |
[] |
Indexation d’un pointeur. |
& |
Obtention de l’adresse mémoire d’une variable. |
++ et -- |
Incrémentation et décrémentation des pointeurs. |
+ et - |
Opérations arithmétiques sur les pointeurs. |
== , != , < , > , <= , >= |
Comparaison de pointeurs. |
stackalloc |
Allocation de mémoire sur la pile. |
fixed |
Fixe temporairement une variable pour obtenir son adresse. |
Exemple d’utilisation des pointeurs
unsafe
{
struct Point { public int X, Y; }
Point p = new Point { X = 10, Y = 20 };
Point* ptr = &p;
Console.WriteLine(ptr->X); // Accès via ->
}
Méthodes de la classe System.Runtime.CompilerServices.Unsafe
Unsafe.As<TFrom, TTo>(ref TFrom source)
: Effectue un cast sans vérification.Unsafe.Read<T>(void* source)
: Lit un objet de la mémoire.Unsafe.Write<T>(void* destination, T value)
: Écrit un objet en mémoire.Unsafe.SizeOf<T>()
: Retourne la taille en mémoire d’un type.Unsafe.CopyBlock(void* destination, void* source, uint byteCount)
: Copie des blocs de mémoire.
int valeur = 42;
float casted = Unsafe.As<int, float>(ref valeur);
Console.WriteLine(casted); // Cast sans conversion (5.9E-44)
3. Exemples concrets d’utilisation
a) Cast unsafe
entre types
int valeur = 42;
float casted = Unsafe.As<int, float>(ref valeur);
Console.WriteLine(casted); // Cast sans conversion (5.9E-44)
b) Boucle foreach
optimisée sur un tableau
L’utilisation des pointeurs permet de contourner certaines vérifications d’accès aux tableaux, améliorant les performances.
unsafe
{
int[] array = { 1, 2, 3, 4, 5 };
fixed (int* ptr = array)
{
int* p = ptr;
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(*(p + i)); // Accès direct sans vérification de bornes
}
}
}
c) Manipulation directe de mémoire avec stackalloc
unsafe
{
int* tableau = stackalloc int[5];
for (int i = 0; i < 5; i++)
{
*(tableau + i) = i * 2;
Console.WriteLine(*(tableau + i));
}
}
d) Copie de mémoire avec Unsafe.CopyBlock
using System;
using System.Runtime.CompilerServices;
unsafe
{
byte[] source = { 1, 2, 3, 4, 5 };
byte[] destination = new byte[5];
fixed (byte* srcPtr = source, destPtr = destination)
{
Unsafe.CopyBlock(destPtr, srcPtr, (uint)source.Length);
}
Console.WriteLine(string.Join(", ", destination)); // 1, 2, 3, 4, 5
}
e) Accès direct aux champs d’une structure
unsafe
{
struct Vector2
{
public float X;
public float Y;
}
Vector2 vec = new Vector2 { X = 3.5f, Y = 7.2f };
Vector2* ptr = &vec;
Console.WriteLine($"X: {ptr->X}, Y: {ptr->Y}");
}
f) Modification d’un tableau en mémoire
unsafe
{
int[] tableau = { 10, 20, 30, 40, 50 };
fixed (int* ptr = tableau)
{
*(ptr + 2) = 100; // Modification de l'élément à l'index 2
}
Console.WriteLine(string.Join(", ", tableau)); // 10, 20, 100, 40, 50
}
Conclusion
Le code unsafe
en C# est un outil puissant pour les cas nécessitant un accès mémoire direct. Il doit être utilisé avec précaution car il introduit des risques liés à la gestion manuelle de la mémoire, mais il est utile pour des scénarios exigeant des performances optimales ou une interopérabilité avec du code natif.