2/19/2009

02-19-09 - Two Code Gems - Appendium

BTW similar to the array size thing is the Zero'er macro. It's very common to write a macro that will zero a struct out, like :

#define ZERO(ptr)  memset(ptr,0,sizeof(*ptr))

BITMAPINFOHEADER bih;
ZERO(&bih);

However, this ZERO() macro is very easy to use in catastrophically bad ways. (and no I'm not talking about invalidating C++ objects or anything - I'm assuming that you are using it only on things that *can* be zeroed, you just accidentally use it wrong).

Exercise : what happens in this code ?


DWORD test1[4] = { 1,1,1,1 };
char * test2[4] = { "","","","" };
char * test3 = "";

ZERO(test1);
ZERO(test2);
ZERO(test3);

(don't use a compiler to cheat).

And how do I write a better ZERO macro ? (I don't actually know the ideal answer to this question, so it's not entirely condescending rhetorical douchebaggery).

9 comments:

Sly said...
This comment has been removed by the author.
Sly said...

Not clean, but it seems to work fine: (replace [] by <> - your blog forbig "html tag" named typename ;) )

template [typename T]
void myZERO(T &ptr, size_t size)
{
printf( "Size=%d\n", size );
memset( &ptr,0,size );
}

template [typename T]
void myZERO(T *ptr, size_t size)
{
size = max( size, sizeof(ptr) );
printf( "SizePtr=%d\n", size );
memset( ptr,0,size );
}

#define ZERO(t) myZERO(t,sizeof(t))

int main()
{
int t[4];
char c;
short s;
int i;
ZERO(t);
ZERO(i);
ZERO(&i);
ZERO(s);
ZERO(c);
}

=======>
SizePtr=16
Size=4
SizePtr=4
Size=2
Size=1

Sly said...

Actually, it will fail for ZERO(&c) and ZERO(&s) :(

cbloom said...

What happens in the sample code :

DWORD test1[4] = { 1,1,1,1 };
char * test2[4] = { "","","","" };
char * test3 = "";

ZERO(test1); // test1[0] = 0
ZERO(test2); // test2[0] = 0
ZERO(test3); // !! access violation! stomping const string

cbloom said...

Just writing the macro slightly differently fixes the majority of problems :

#define ZERO(obj) memset(&(obj),0,sizeof(obj))

is much better.

If you want something that's more restrictive you could do :


template typename T
void CheckedZero(T * ptr)
{
const T zero = { 0 }; // will fail compile if T has constructors
memset(ptr,0,sizeof(T));
}

#define CHECKED_ZERO(obj) CheckedZero(&(obj))

cbloom said...

Hmm.. actually the simple ZERO(obj) thing on test3 (the char *) just sets the pointer to null, which is not really what you want.

I'd like that to be a compile error. I think Sly's way is good, but I would make the pointer version of myZERO just be a compile failure (and make one that catches arrays).

Sly said...

> char * test3 = "";
> ZERO(test3); // !! access violation! stomping const string

That's because of a C/C++ weirdness: we're allowed to take non const pointers on const string char buffers. The crash will also occurs in this supposedly valid code:
char * test3 = "";
*test3 = 0;

Changing the test code to this will make it work:
char * test3 = strdup("");

Sly said...

Actually what one probably want to do instead is: ZERO(&test3) ;)

But it does prove your point saying there's no perfectly safe ZERO stuff.

cbloom said...

Yeah, ideally the cases like ZERO(test3) that are just totally ambiguous and obviously wrong I would like to just be a compile error.

old rants