C# - Unsafe kód a ukazatele
Co tento článek ukazuje
- Základy práce s ukazateli v C#
- Klíčová slova
unsafe
,fixed
asizeof
- Práce s bitmapou pomocí ukazatelů
Častým mylným argumentem proti jazyku C# je, že v tomto jazyce není narozdíl od C++ možné pracovat s ukazateli. V C# lze s ukazateli pracovat díky takzvanému "unsafe kódu", ale pravdou je, že to ve většině případů není potřeba. Tento článek ukazuje jakým způsobem lze v C# přímo přistupovat do paměti a na závěr i jeden příklad, který ukazuje kdy je vhodné ukazatele použít.
Práce s ukazateli v C#
Klíčové slovo unsafe
Následující příklad ukazuje, jak lze napsat funkci, která
přes ukazatele zapisuje hodnoty do pole typu int[]
.
Tato funkce bere jako parametr ukazatel na pole a předpokládá, že velikost
pole je nejméně 500 položek. Toto je velmi nebezpečné chování, protože
pole může být klidně menší a v tom případě by funkce mohla přepsat
jiná data v paměti (a nemuselo by to způsobyt vyjímku). Klíčové slovo unsafe
,
které je uvedeno v deklaraci metody označuje tuto metodu za "nebezpečnou" a
umožňuje v ní pracovat s ukazateli (Toto klíčové slovo lze dle potřeby uvést i
k celé třídě nebo pouze jako blok v metodě).
// Naplnuje pole bytu primym pristupem do pameti
// ptr - ukazatel na pole int[]
private unsafe static void UnsafeMethod(int* ptr)
{
for(int i=0; i<500; i++) {
*ptr=i; ptr++;
}
}
Z předcházejícího příkladu je zřejmé, že stejně jako v C/C++ i v C#
platí ukazatelová aritmetika a je tedy možné ukazatel na nějaký
datový typ posouvat po jednotlivých položkách v poli pomocí operátoru
++
(a samozřejmě i dalších).
Pokud se pokusíte tento kód zkompilovat, compiler ohlásí chybu "Unsafe code may only appear if compiling with /unsafe", což znamená, že je potřeba přidat compileru (csc.exe) jako parametr /unsafe a nebo ve VS.Net ve vlastnostech projektu nastavit Allow Unsafe Code Blocks.
Klíčové slovo fixed
Nyní když máme metodu, která naplňuje pole čísly, je ještě potřeba
ukázat jak jí zavolat. K tomu je potřeba použít klíčové slovo fixed
,
pomocí kterého lze označit blok kde pracujeme s ukazatelem na pole.
Tímto klíčovým slovem .NET Frameworku zakážeme přesunutí objektu,
takže ukazatel zůstane po celou dobu platný (jeho použití je opět možné pouze
v unsafe bloku):
unsafe static void Main(string[] args)
{
// vytvori pole a preda ukazatel
int[] buffer=new int[500];
fixed(int* p=buffer)
{
UnsafeMethod(p);
}
}
Operátor sizeof
Další důležitou součástí C# při práci s ukazateli je (stejně jako v C++)
operátor sizeof
, pomocí kterého je možné zjistit velikost
jednotlivých hodnotových datových typů (value types) v paměti.
Nebezpečný kód
Již podle klíčového slova unsafe
lze poznat, že práce s ukazateli
není v C# myšlena jako věc, bez které se člověk při každodení práci neobejde.
V následující části naleznete jednu ukázku, která rozhodně neukazuje
jak lze rozumně používat ukazatele, proto jí prosím berte s rezervou.
Jedná se spíše o demnostraci toho, že ukazatele jsou i v C# poměrně všemocným
nástrojem. Následující kód by se tedy rozhodně neměl objevit v žádné
reálné aplikaci (a prosím čtenáře se slabším žaludkem, ať následující odstavec přeskočí).
Tato ukázka vychází z toho, že .NET si před řetězec ukládá do 4 bytů
(tedy datový typ int
) délku řetězce. Tuto délku zjišťuje tak,
že získá ukazatel na začátek řetězce, přetypuje ho na ukazatel typu int*
a posune se na předcházející položku (tedy o 4 byty před řetězec) a přečte délku.
string str="Hello world"; fixed(char* ptr=str) { // ziskame ukazatel na retezec int* p=(int*)ptr; // presuneme se na delku (-4 byty) p--; Console.WriteLine("Delka: {0}",*p); }
Práce s obrázky
Jedním s mála případů, kdy je velmi vhodné použít ukazatele je situace,
kdy potřebujete přistupovat přímo k datům v bitmapě. Třída Bitmap
sice obsahuje metody pro zjištění a nastavení barvy pixelu, ale tato metoda
je řádově pomalejší. O práci s obrázky jsem již dříve psal (viz [1]),
ale v tomto článku bude celý kód lépe popsaný a vysvětlený. Více o práci
s obrázky můžete najít v seriálu Christiana Grausse [2].
V následující (zjednodušené) ukázce vyplníme bitmapu náhodnými body a
složitější příklad (viz screenshot) naleznete v přiložených zdrojových kódech.
Objekt Bitmap
obsahuje metodu LockBits
,
pomocí které lze získat přístup k datům v bitmapě. Tato metoda vrací strukturu,
která mimo jiné obsahuje ukazatel na začátek dat bitmapy. Jako parametr této
metodě můžete specifikovat formát v jakém mají být data k dispozici.
V této ukázce budeme pracovat s formátem Format32bppArgb
,
který pro každý pixel ukládá barvu jako 4 byty (int
), kde
1 byte je průhlednost a další tři byty tvoří barvu ve formátu RGB.
Pokud máte barvu v tomto formátu, je možné zkonvertovat ji na strukturu
Color
pomocí Color.FromArgb
a zpět metodou
Color.ToArgb
.
// Vytvori novou bitmapu Bitmap ret=new Bitmap(320,200); // Ziska pristup k datum bitmapy BitmapData data=ret.LockBits( new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.WriteOnly,PixelFormat.Format32bppArgb);
V následující ukázce naleznete kód, který postupně prochází všechny
řádky a sloupce v bitmapě a každému pixelu nastaví náhodnou barvu.
Ukazatel na začátek bitmapy je položka data.Scan0
ze
struktury BitmapData
. Tato struktura dále obsahuje
položku Stride
, která určuje o kolik bytů je třeba
posunout ukazatel při přechodu u řádku, protože řádka může v paměti
zabírat o něco více než je šířka bitmapy. V této ukázce je tedy
nejpodstatnější výpočet pozice na které začíná y-tá řádka bitmapy
(po pixelech na řádce se již ukazatel posouvá pomocí operátoru ++
).
// generator nahodnych cisel Random rnd=new Random(); // postupne prochazime po radcich for(int y=0; y<data.Height; y++) { // vypocte ukazatel na zacatek y-teho radku int* retPos=(int*)((int)data.Scan0+(y*data.Stride)); int x=0; while(x<data.Width) { // vyplni pixel nahodnou barvou *retPos=Color.FromArgb(rnd.Next(256), rnd.Next(256),rnd.Next(256)).ToArgb(); // posun na dalsi pixel retPos++; x++; } }
Na závěr je ještě potřeba uvolnit data, získaná pomocí LockBits
a
bitmapa je vytvořena.
// Uvolnit data
ret.UnlockBits(data);
Soubory na stažení a odkazy
- [1] Vytváření černobílé (stupně šedi) bitmapy z barevné [^]
- [2] Christian Graus - Image Processing for Dummies [^] - CodeProject.com