Don’t Count Much for Nothing


 
Hot licks and rhetoric
Don’t count much for nothing
– "Throw Back the Little Ones," by Steely Dan (from the album Katy Lied, 1975)
Many C++ programmers are familiar with the following macro. It’s used to determine the length of an array.
#define ARRAYSIZE(Array) (sizeof(Array)/sizeof(Array[0]))
ARRAYSIZE returns 6 in the following code
char Hello[6] = "Hello";
const int i = ARRAYSIZE(Hello); // i == 6
However, ARRAYSIZE can be dangerous at times. Consider
void foo( const char s[] )
{
   memset( (void*)s, 0, ARRAYSIZE(s) ); // Danger, Will Robinson!
   // …
}
 
char Hello[6] = "Hello";
char* pStr = new char [6]; 
foo( Hello );
foo( pStr );
The programmer had a reasonable intent here, but the end result is all wrong. In fact, the end result is downright dangerous. Inside foo(), sizeof(s) is the size of a const char []. That’s the same as the size of a pointer (4 or 8 bytes). Even though it’s clear to the programmer that both calls to foo have a 6-character string, that information has been lost within foo(). Don’t count much for nothing. On a 32-bit platform, the first four bytes of Hello and pStr will be cleared out. On a 64-bit platform, the first 8 bytes will be cleared out, overwriting the stack (in the case of Hello) and the heap (in the case of pStr). Truly deadly, and something that could go undetected for a long time.
 
C++ templates to the rescue. Here’s a safer way to define ARRAYSIZE:
template <typename SizeType, size_t Size>
char ( *ArraySizeHelper( SizeType ( &Array )[Size]) )[Size];
 
#define ARRAYSIZE(Array) sizeof(*ArraySizeHelper(Array))
Don’t worry if you can’t parse this easily — there’s some complicated template mojo going on here involving an array of template function pointers. In the nominal case, this definition produces the same results as the original definition of ARRAYSIZE: a constant value equal to the number of elements in the array. However, if Array isn’t really an array, you get a compiler error, which is exactly what you want! The key part of the compiler error will be something like "could not deduce template argument." In other words, the compiler determined that Array was not an array. This allows you to use ARRAYSIZE safely. In the foo() example above, the call to the new ARRAYSIZE in memset generates a compiler error. Life is good.
 
Visual C++ 2005 encapsulates this magic in stdlib.h as the _countof macro. I highly recommend this new method. Use it replace ARRAYSIZE in your own code. Then let me know how many latent bugs you find 🙂
Advertisements
This entry was posted in C++. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s