Pointers are often used in lower-level languages, such as C, as an efficient way to access and perform operations on memory buffers. In C, as with many other languages which allow their use, a pointer is simply a variable with a value: that value is the address of a region of memory, and the memory can be accessed by dereferencing the pointer.
The current generation of languages have generally eschewed pointers, and most big thinkers in programming discourage their use. As The C Programming Language states:
Pointers have been lumped with goto
statement as a marvelous way
to create impossible-to-understand programs. ... With discipline, however,
pointers can be used to achieve clarity and simplicity.
It's for this reason that the team behind the development of C# ran against the trend of removal for pointer syntax, and included pointers in the language.
Pointers and unsafe
blocks
C# is one of the languages in the .NET family, and as such is run under the Common Language Runtime (CLR). It's the runtime that takes care of memory operations and lower-level functionality on the program's behalf, so that the program doesn't normally have to worry about pointers or memory buffers.
The language does, however, provide a way for pieces of code to avoid the
constraints of the CLR. Any blocks marked as unsafe
will not be
managed by the runtime, and it's up to the programmer to test and ensure that
the code works as expected.
An unsafe
block in a method
static void foo() { int i = 10; unsafe { int *p = &i; System.Console.WriteLine("Value at p: " + *p); System.Console.WriteLine("Address of p: " + (int)p); } }
As can be seen in this example, pointer dereferencing and casts are both
allowed to happen inside an unsafe
block, such that pointer
operations within these blocks proceed much as they would under C.
A graphic example: The fire effect
One of the first things a budding coder wants to learn is how to make a game, and the first stage in that journey is how to output graphics to the screen. Among the simplest demonstrations of graphical output are the two-dimensional colour effects: palette rotation, plasma, and the effect being explored here, fire.
The effect relies on the screen being represented as a memory buffer, running left-to-right and top-down; accessing consecutive memory locations will run through the whole buffer in sequence. An averaging algorithm is applied across the buffer, which runs as follows:
Fire algorithm, applied to a memory buffer
// Introduce randomness to the averagingFor each X-coordinate on the bottom line Fill in the pixel with a random value Next// Apply averaging filterFor all other lines in the buffer For each X-coordinate on the line Total = Value of current pixel + Value of pixel to the right + Value of pixel underneath + Value of pixel to the bottom left Avg = Total / 4// Decrement, so lines toward the top fade to 0Avg = Avg-1 If (Avg < 0) Avg = 0 Value of current pixel = Avg Next Next
The effect of this is that the larger values, from the line beneath, are transferred in lesser form up the screen; combined with a forced decrement on the averaged value, the effect is a randomised fading from high values at the bottom, to zero at the top. With the appropriate palette to define high values as white, going through yellow and red to black, it's easy to make this look like a burning fire.
It's a situation that's ideal for pointers: dereferencing a pointer to get the value at the current pixel, adding constants on to get to the pixels around the current one, and then pushing the pointer along to do the next byte.
Putting the effect on-screen
The simplest way of using this effect to get something on the screen is to use a Windows Form; the Form base class will handle all the window instantiation and mouse events, leaving us to concentrate on putting data into the window. By holding a handle to an 8-bit bitmap image, and drawing that image into the window, the Bitmap class will do all the work of calculating palette colours and translating them from the bitmap indices.
fire.cs
: Rendering the fire
using System; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; class FirstForm : Form { private Bitmap buf;// Graphic bufferprivate Random rnd;// RNG sourceprivate const int width = 320; private const int height = 240; public FirstForm() {// Initialise an RNG for later usernd = new Random();// Set the initial properties of the formthis.Text = "Fire #1"; this.ClientSize = new Size(width, height); this.MaximizeBox = false; this.BackColor = Color.Black; SetStyle(ControlStyles.Opaque, true);// Nominate the paint functionthis.Paint += new PaintEventHandler(this.DoFire);// Generate a 320x240 bitmap, fire palettebuf = new Bitmap(width, height, PixelFormat.Format8bppIndexed); ColorPalette pal = buf.Palette;// Fill the palette with the following 64-colour blocks: // Black to red, Red to yellow, Yellow to white, White // Since each range is 64 colours, and RGB spans 256 values, // utilise the left shift to multiply upfor(int i=0; i<64; i++) { pal.Entries[i] = Color.FromArgb(i<<2, 0, 0); pal.Entries[i+64] = Color.FromArgb(255, i<<2, 0); pal.Entries[i+128] = Color.FromArgb(255, 255, i<<2); pal.Entries[i+192] = Color.FromArgb(255, 255, 255); } buf.Palette = pal; }// The paint function delegated to handle drawing the fireprivate void DoFire(object src, PaintEventArgs e) {// Lock the bitmap so we can write to it directBitmapData buflock = buf.LockBits( new Rectangle(Point.Empty, buf.Size), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);// Write a fire // This section uses pointers, and is thus deemed "unsafe"unsafe {// Fetch a pointer to the top scanline of the imageByte *bufdata = (Byte*)buflock.Scan0; Byte *bufbottom = bufdata + ((height-1) * width); Byte *i; int v;// Write a random bottom line as source of the firefor(int x=0; x<width; x++) { *(bufbottom+x) = (Byte)rnd.Next(0, 255); }// For each pixel in the image, average the values of // the pixel, the one to the right, the one underneath // and the one to the bottom left. Threshold to 0, // and write to the current position.for(i=bufdata; i<bufbottom; i++) { v = *i + *(i+1) + *(i+height) + *(i+height-1); v /= 4; if(v<0) v=0; *i = (Byte)v; } }// Unlock ourselves out from the image and blit it to the Formbuf.UnlockBits(buflock); e.Graphics.DrawImageUnscaled(buf, 0, 0);// Ensure that we'll be drawing another frame real soon, by // forcing a repaintthis.Invalidate(); } public static void Main(string[] args) { Application.Run(new FirstForm()); } }
In the full example above, the unsafe
block in the paint
handler is where the averaging algorithm is applied across the bitmap buffer,
through the use of pointers. By running this code for a few seconds, the
following is produced on-screen.
So that's how it works. Some languages in the current crop forbid the use of pointers altogether: C# allows their use, but only if you promise to keep things clean, because the runtime won't do it for you.
Copyright Imran Nazar <tf@imrannazar.com>, 2008.