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:

  1. This comment has been removed by the author.

    ReplyDelete
  2. 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

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

    ReplyDelete
  4. 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

    ReplyDelete
  5. 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))

    ReplyDelete
  6. 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).

    ReplyDelete
  7. > 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("");

    ReplyDelete
  8. 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.

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

    ReplyDelete