0

I'm trying to design a macro to produce several related data structures related to things that need initialization. The code has to compile under both C and C++. The goal is to have something like:

  MUNGE_THING(struct1);
  MUNGE_THING(array1);

turn into something equivalent to

  munge_thing((void*)&struct1, sizeof(struct1));
  munge_thing((void*)array1, sizeof(array1));

Is there any syntactic stuff I can surround the macro argument with so that it will handle both arrays and structure correctly both when taking the address and when getting the size? The most likely context will be in the constant declaration of an initialization list.

If that isn't possible, and it's necessary to use separate macros for structures and arrays, what would be the best syntax to ensure that passing something incorrectly will yield a compile error rather than bogus code?

In "old" C, prepending an array address with "&" would yield a warning, but not prevent compilation. In C++, it seems to yield the address of a location which stores the address of the array.

The MUNGE_THING macros are going to be within another macro that will be invoked multiple times with different definitions of MUNGE_THING, so having separate macros for arrays and structs would be irksome. The best approach I can figure would be to give MUNGE_THING an extra argument for the "optional" ampersand, but that somehow seems ugly.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    In case different definitions are needed for C and C++, you may use #ifdef __cplusplus – rwong Aug 06 '10 at 14:57

3 Answers3

5

If the array is in fact an array (which seems to be required for the sizeof to work), why don't you just use the simple macro:

#define MUNGE_THING( x ) munge_thing((void*)&(x), sizeof(x))

That should work both for arrays and structs:

int array[10];
assert( (void*)array == (void*)&array );

You have tagged the question as both C and C++, in C++ you can use templates and avoid the macros all together.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • You can't tell in munge_thing(p,n) if `p` points to `n` bytes or if `p` points to a pointer which points to `n` bytes. – Nordic Mainframe Aug 06 '10 at 14:59
  • Note that `sizeof(&array)` yields the size of the whole array while `sizeof(array)` gives you the size of the first element of the array. This macro may still not work out. – nmichaels Aug 06 '10 at 15:03
  • @David Rodríguez - dribeas: As I noted, this particular code has to compile as both C and C++; templates would make it usable for C++ only. For a C++ only project templates might be useful. – supercat Aug 06 '10 at 15:32
  • @Nathon: that is exactly wrong. `sizeof arr` yields the size in bytes of the entire array, while `sizeof &arr` yields the size of a pointer to that array. 6.3.2.1 para 3: " **Except when it is the operand of the sizeof operator** or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue." – John Bode Aug 06 '10 at 15:41
  • I don't quite understand the comments by @Luther Blissett or @Nathon. @Luther: in `munge_thing` you receive a `void*` and an `int`. You cannot say anything at all there --I can only assume that this will just be used to work on the raw bytes (sending over the wire?). @Nathon: The address of the array and the address of the first element are the same. `(void*)array` is `(void*)(&array[0])` and that is `(void*)&array`. Note that I have not applied the `&` operator in `sizeof`, only to the pointer side. The code is correct as it stands. – David Rodríguez - dribeas Aug 06 '10 at 17:16
  • @David: The raw bytes are two indirections away if you pass an array, but only one indirection away if you pass the struct. For example, `void mungle(void*p,size_t n) { fwrite(p,n,1,some_file); }` writes the raw bytes from a struct, but rubbish from an array. – Nordic Mainframe Aug 06 '10 at 17:37
  • Unless there's away to tell how the actual data can be found, the pointer passed to mungle is useless. – Nordic Mainframe Aug 06 '10 at 17:38
  • Whoops, John, you're right. I got it backwards. I should have labeled the outputs of my little program. – nmichaels Aug 06 '10 at 17:57
  • @Luther Blissett: what is the concrete definition of array that you are referring to? In a real array (not a block of memory allocated with `new []`, the address of the array and the array of the first element are the same value. `int array[10]; assert( (void*)array == (void*)(&array));` Which is different from: `int *p = new int[10]; assert( (void*)p != (void*)&p );`. In the first case it *is* a real array, in the second case it is a pointer. Arrays and pointers are *not* the same thing. -- Or maybe I am not understanding what you are trying to tell me. – David Rodríguez - dribeas Aug 07 '10 at 20:47
  • Oh I see the problem, I always had the degraded array case in mind. YOu are right of course. Taking an address of an non-degraded array should be ok. +1 – Nordic Mainframe Aug 07 '10 at 21:51
2

I'm not sure what problem you are having with &array1. This C++ worked exactly as expected (all values the same)

int main(int argc, char* argv[])
{
    int array1[10];

    printf("%x %x\n", array1, &array1);
    cout << array1 << " " << &array1 << endl;

    void* ptr1 = array1;
    void* ptr2 = &array1;

    printf("%x %x\n", ptr1, ptr2);
    cout << ptr1 << " " << ptr2 << endl;
    return 0;
}
James Curran
  • 101,701
  • 37
  • 181
  • 258
  • 1
    The why is discussed here http://stackoverflow.com/questions/2528318/c-how-come-an-arrays-address-is-equal-to-its-value – torak Aug 06 '10 at 14:53
0

Okay, I see my confusion. In C++, the type of &array is not compatible with the type of the array, and as the linked discussion notes, (&array)+1 is not the same as (array+1), but casting the unsubscripted pointers does in fact yield the proper results. The distinctions between arrays and pointers in C are very confusing. Thanks for the assistance.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • The array and the first element are aligned in memory, so `&array` and `&array[0]` are the same address, even if different types. The tricky part is that arrays *decay* very easily into a pointer to the first element (whenever they are used as an rvalue), so `void *p = array;` is processed by the compiler as `void *p = &array[0]`, that is, the array decays into a pointer to the first element --which is of the contained type. That difference in types is what makes `(void*)((&array)+1) != (void*)(array + 1)` (the left hand side there is another example of the decay) – David Rodríguez - dribeas Aug 06 '10 at 17:21