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 averaging For each X-coordinate on the bottom line Fill in the pixel with a random value Next // Apply averaging filter For 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 0 Avg = 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 buffer private Random rnd; // RNG source private const int width = 320; private const int height = 240; public FirstForm() { // Initialise an RNG for later use rnd = new Random(); // Set the initial properties of the form this.Text = "Fire #1"; this.ClientSize = new Size(width, height); this.MaximizeBox = false; this.BackColor = Color.Black; SetStyle(ControlStyles.Opaque, true); // Nominate the paint function this.Paint += new PaintEventHandler(this.DoFire); // Generate a 320x240 bitmap, fire palette buf = 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 up for(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 fire private void DoFire(object src, PaintEventArgs e) { // Lock the bitmap so we can write to it direct BitmapData 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 image Byte *bufdata = (Byte*)buflock.Scan0; Byte *bufbottom = bufdata + ((height-1) * width); Byte *i; int v; // Write a random bottom line as source of the fire for(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 Form buf.UnlockBits(buflock); e.Graphics.DrawImageUnscaled(buf, 0, 0); // Ensure that we'll be drawing another frame real soon, by // forcing a repaint this.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.

Fire algorithm screenshot Figure 1: The fire algorithm applied to a bitmap buffer

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.

Article dated: 1st Nov 2008

Get the RSS feed