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))


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 = "";


(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).


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;


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

#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