Az unsafe vezérlési szerkezet egy speciális kódrészletet jelöl C# esetén. Ebben az üzemmódban lehetséges mutatókat használni.
A C és C++ nyelv legnehezebben tanulható és megérthető része a mutatók kezelése. A mutatók, más néven pointerek segítségével dinamikus memóriakezelést tudunk megvalósítani a programunkban. A dinamikus memóriakezelés azért hasznos, mert így egy adott művelethez annyi memóriát tud foglalni a programunk, amennyire szüksége lesz.
Továbbá mutatók segítségével gyorsabban hozzá tudunk férni a memória egy részletéhez, így optimalizálni lehet bizonyos lassan működő kódrészletek működését, valamint ilyen kontextusban hatékonyan és gyorsan van lehetőségünk együttműködni natív, nem menedzselt környezetből származó kóddal. Az unsafe vezérlési szerkezet csak akkor használható, ha ez külön be van kapcsolva a projekt fordítási beállításainál.
A beállítást a projekt tulajdonságain belül a build menüpontban érhetjük el. A tulajdonságok lap előhozása: Solution Explorerben jobb kattintás a projekt nevén → Properties → Build menüpont kiválasztása
Felmerülhet a kérdés, hogy miért nehezítik meg ennyire ezen szintaxis használatát? A válasz egyszerű. Az unsafe módban kezelt memória a .NET szemétgyűjtője által nem kezelhető területre kerül. Ennek az oka az, hogy ha az operációs rendszerrel, vagy más natív komponensekkel társalog a programunk, akkor nem lenne túl szerencsés, hogyha a szemétgyűjtő éppen átvitel közben törölne valami fontos adatot a memóriából.
Ezért ezen memóriaterületek felszabadításáért és menedzseléséért a felhasználó a felelős. Ha egy ilyen kódrészletben használt erőforrást nem megfelelően vagy egyáltalán nem szabadítunk fel, akkor memóriaszivárgás keletkezik.
A memóriaszivárgás egy alattomos és nehezen felderíthető hiba típus, mivel azonnal nem jelentkezik a program halálával, hanem szépen lassan jön elő. A hiba lényege, hogy a lefoglalt memóriaterületek nem szabadulnak fel. Ezáltal új változók, újabb adatok csak olyan memóriaterületre helyezhetőek el, amelyek még nem foglaltak. Viszont ezen memóriaterületek sem szabadulnak fel használat után, így az alkalmazás a memóriában egyre csak nő és nő, egészen addig, amíg a memória teljesen el nem fogy, vagy amíg az operációs rendszer nem korlátozza le a memória használatot.
A memória használat lekorlátozása csak 32 bites folyamatok esetén történik meg. Windows alatt egy 32 bites folyamat „csak” 2 GiB memóriával gazdálkodhat. Ha a memória igénye ezen limit fölé kerül, akkor az operációs rendszer leállítja az adott folyamatot.
64 bites rendszerek esetén nincs memória korlát a folyamatokra, így egy szivárgó folyamat akár több száz GiB memóriát is lefoglalhatna. Természetesen, mivel nincs senkinek sem ennyi memóriája (még), előbb-utóbb a rendszer ki fog futni a memóriából.
Ekkor kezdődik meg az operációs rendszer haláltusája. Az éppen nem futó programokat a memóriából elkezdi kivenni és kiírni a merevlemezre1, a virtuális memóriába, hogy a szivárgó folyamatnak legyen elég hely a memóriában.
Viszont a virtuális memória sebessége legalább 10x lassabb a rendszermemóriánál. Ezért a rendszer belassul, használhatósága közelít a nullához, majd mikor a virtuális memória is megtelt, akkor két lehetőség előtt áll az operációs rendszerünk. Ha sikerül neki, akkor bezárja a folyamatot, viszont ha eléggé lelassult a gép és a programunk igen gyorsan szemetel, akkor belefagyhat a folyamatba.
Az unsafe módban használt változók megfelelő felszabadításáról a Finalizer, IDisposable és memória kezelés alfejezetben lesz részletesebben szó.
Az alábbi példakód az unsafe mód használatát mutatja be:
using System;
namespace PeldaUnsafe
{
class Program
{
static unsafe void Negyzet(int* p)
{
*p *= *p;
}
unsafe static void Main()
{
int i = 5;
//& érték átadás, mint C esetén
Negyzet(&i);
Console.WriteLine(i);
Console.ReadKey();
}
}
}
A program kimenete:
25
Az unsafe jelölhet egy metóduson belül blokkot is, vagy mint ahogy a példában látható, jelölheti az egész metódust. Unsafe metódus esetén nem elég csak azt a metódust metódust megjelölni a kulcsszóval, ami ilyen kódot tartalmaz. A hívó metódust is meg kell jelölni az unsafe kulcsszóval.
Az alábbi példaprogram a pointerek működését szemlélteti egy kicsit jobban:
using System;
namespace PeldaUnsafe3
{
class Program
{
static unsafe void Main()
{
int x = 10;
short y = -1;
byte y2 = 4;
double z = 1.5;
int* pX = &x;
short* pY = &y;
double* pZ = &z;
Console.WriteLine("x Címe: 0x{0:X}, Mérete: {1}, Értéke: {2}", (uint)&x, sizeof(int), x);
Console.WriteLine("y Címe: 0x{0:X}, Mérete: {1}, Értéke: {2}", (uint)&y, sizeof(short), y);
Console.WriteLine("y2 Címe: 0x{0:X}, Mérete: {1}, Értéke: {2}", (uint)&y2, sizeof(byte), y2);
Console.WriteLine("z Címe: 0x{0:X}, Mérete: {1}, Értéke: {2}", (uint)&z, sizeof(double), z);
Console.WriteLine("pX=&x Címe: 0x{0:X}, Mérete: {1}, Értéke: 0x{2:X}", (uint)&pX, sizeof(int*), (uint)pX);
Console.WriteLine("pY=&y Címe: 0x{0:X}, Mérete: {1}, Értéke: 0x{2:X}", (uint)&pY, sizeof(short*), (uint)pY);
Console.WriteLine( "pZ=&z Címe: 0x{0:X}, Mérete: {1}, Értéke: 0x{2:X}", (uint)&pZ, sizeof(double*), (uint)pZ);
*pX = 20;
Console.WriteLine("*pX, x = {0} után", x);
Console.WriteLine("*pX = {0}", *pX);
pZ = (double*)pX;
Console.WriteLine("x, mint double típus = {0}", *pZ);
Console.ReadLine();
}
}
}
A program kimenete:
x Címe: 0xF133D634, Mérete: 4, Értéke: 10
y Címe: 0xF133D630, Mérete: 2, Értéke: -1
y2 Címe: 0xF133D62C, Mérete: 1, Értéke: 4
z Címe: 0xF133D620, Mérete: 8, Értéke: 1,5
pX=&x Címe: 0xF133D618, Mérete: 8, Értéke: 0xF133D634
pY=&y Címe: 0xF133D610, Mérete: 8, Értéke: 0xF133D630
pZ=&z Címe: 0xF133D608, Mérete: 8, Értéke: 0xF133D620
*pX, x = 20 után
*pX = 20
x, mint double típus = -2,01847224230674E+237
-
Abban az esetben, ha a lapozófájl használata be van kapcsolva. Alapértelmezett beállításként be van kapcsolva.↩