tags:

views:

1226

answers:

8

Hi,

I am using the following macro for calculating size of an array:

#define G_N_ELEMENTS(arr) ((sizeof(arr))/(sizeof(arr[0])))

However I see a discrepancy in the value computed by it when I evaluate the size of an array in a function(incorrect value computed) as opposed to where the function is called(correct value computed). Code + output below. Any thoughts, suggestions, tips et al. welcome. Thanks.

DP

#include <stdio.h>


#define G_N_ELEMENTS(arr) ((sizeof(arr))/(sizeof(arr[0])))


void

foo(int * arr) //Also tried foo(int arr[]), foo(int * & arr) 
{              // - neither of which worked
  printf("arr : %x\n", arr);
  printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));
}

int
main()
{
  int arr[] = {1, 2, 3, 4};

  printf("arr : %x\n", arr);
  printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));

  foo(arr);
}

Ouput: 
arr : bffffa40
sizeof arr: 4
arr : bffffa40
sizeof arr: 1
+10  A: 

This isn't working because sizeof is calculated at compile-time. The function has no information about the size of its parameter (it only knows that it points to a memory address).

Consider using an STL vector instead, or passing in array sizes as parameters to functions.

Marcel Guzman
A: 

You should only call sizeof on the array. When you call sizeof on the pointer type the size will always be 4 (or 8, or whatever your system does).

MSFT's Hungarian notation may be ugly, but if you use it, you know not to call your macro on anything that starts with a 'p'.

Also checkout the definition of the ARRAYSIZE() macro in WinNT.h. If you're using C++ you can do strange things with templates to get compile time asserts if do it that way.

jeffamaphone
You can call sizeof() on the pointer, but the size you get will be the size of the pointer, not the size of the array.
Jonathan Leffler
Good point. Let me fix that.
jeffamaphone
+1  A: 

If you change the foo funciton a little it might make you feel a little more comfortable:

void foo(int * pointertofoo) 
{              
   printf("pointertofoo : %x\n", pointertofoo);  
   printf ("sizeof pointertofoo: %d\n", G_N_ELEMENTS(pointertofoo));
}

That's what the compiler will see something that is completely a different context than the function.

ojblass
+16  A: 

That's because the size of an int * is the size of an int pointer, 4 or 8 bytes on modern platforms that I use but it depends entirely on the platform. The sizeof is calculated at compile time, not run time, so even sizeof (arr[]) won't help because you may call the foo() function at runtime with many different-sized arrays.

The size of an int array is the size of an int array.

This is one of the gotchas in C/C++ - the use of arrays and pointers are not always identical.

One of two solutions, compatible with both C and C++:

  • pass the length in with the array (not useful if the function is meant to work out the array size).
  • pass a sentinel value marking the end of the data, e.g., {1,2,3,4,-1}.
paxdiablo
+3  A: 

Note that even if you try to tell the C compiler the size of the array in the function, it doesn't take the hint (my DIM is equivalent to your G_N_ELEMENTS):

#include <stdio.h>

#define DIM(x)  (sizeof(x)/sizeof(*(x)))

static void function(int array1[], int array2[4])
{
    printf("array1: size = %u\n", (unsigned)DIM(array1));
    printf("array2: size = %u\n", (unsigned)DIM(array2));
}

int main(void)
{
    int a1[40];
    int a2[4];
    function(a1, a2);
    return(0);
}

This prints:

array1: size = 1
array2: size = 1

If you want to know how big the array is inside a function, pass the size to the function. Or, in C++, use things like STL vector<int>.

Jonathan Leffler
+1. The fact that C++ at least (and maybe C, I'm not sure) doesn't honour the size given, or even give a warning/error, is pretty reprehensible IMHO.
j_random_hacker
Yeah, I was just wondering about sizeof(complete-array-type parameters) myself.
aib
+1  A: 
foo(int * arr) //Also tried foo(int arr[]), foo(int * & arr) 
{              // - neither of which worked
  printf("arr : %x\n", arr);
  printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));
}

sizeof(arr) is sizeof(int*), ie. 4

Unless you have a very good reason for writing code like this, DON'T. We're in the 21st century now, use std::vector instead.

For more info, see the C++ FAQ: http://www.parashift.com/c++-faq-lite/containers.html

Remember: "Arrays are evil"

Jimmy J
Shouldn't that be: "we're in the 21st century now, there's no need to use C++ anymore" ;-)
JesperE
Have you got something more suitable...? :-)
Jimmy J
+6  A: 

In C++, you can define G_N_ELEMENTS like this :

template<typename T, size_t N> 
size_t G_N_ELEMENTS( T (&array)[N] )
{
  return N;
}

If you wish to use array size at compile time, here's how :

// ArraySize
template<typename T> 
struct ArraySize;

template<typename T, size_t N> 
struct ArraySize<T[N]> 
{ 
  enum{ value = N };
};

Thanks j_random_hacker for correcting my mistakes and providing additional information.

Benoît
This is a great alternate definition that won't compile if it's passed a pointer. +1
Mark Ransom
Although, I'd make the parameter a const reference.
Mark Ransom
Although you may need the value at compile time, in which case a better alternative is: template<typename T, size_t N> struct G_N_ELEMENTS; template<typename T, size_t N> struct G_N_ELEMENTS<T ( };
j_random_hacker
j_random_hacker
Thank you all, i'll update my answer.
Benoît
+1. EDIT: I fixed the definition of the primary ArraySize template (it must have 1 parameter, not 2) and added a trailing semicolon to the enum declaration.
j_random_hacker
Yep, thanks again :)
Benoît
Removed the comments as the code is already corrected. Still some usage samples would be nice.
David Rodríguez - dribeas
+2  A: 

I'd like to build a little on Benoît's answer.

Rather than passing just the starting address of the array as a pointer, or a pointer plus the size as others have suggested, take a cue from the standard library and pass two pointers to the beginning and end of the array. Not only does this make your code more like modern C++, but you can use any of the standard library algorithms on your array!

I hope this works, I'm making it up as I go along. I've already had to edit this for const correctness and proper syntax (thanks j_random_hacker). I still get compile errors from MS VC++6, won't get a chance to try a newer compiler until tomorrow.

template<typename T, int N>
T * BEGIN(T (& array)[N])
{
    return &array[0];
}

template<typename T, int N>
T * END(T (& array)[N])
{
    return &array[N];
}

template<typename T, int N>
const T * BEGIN_CONST(const T (& array)[N])
{
    return &array[0];
}

template<typename T, int N>
const T * END_CONST(const T (& array)[N])
{
    return &array[N];
}

void
foo(int * begin, int * end)
{
  printf("arr : %x\n", begin);
  printf ("sizeof arr: %d\n", end - begin);
}

int
main()
{
  int arr[] = {1, 2, 3, 4};

  printf("arr : %x\n", arr);
  printf ("sizeof arr: %d\n", END(arr) - BEGIN(arr));

  foo(BEGIN(arr), END(arr));
}

Here's an alternate definition for BEGIN and END, if the templates don't work.

#define BEGIN(array) array
#define END(array) (array + sizeof(array)/sizeof(array[0]))

Update: The above code with the templates works in MS VC++2005 and GCC 3.4.6, as it should. I need to get a new compiler.

I'm also rethinking the naming convention used here - template functions masquerading as macros just feels wrong. I'm sure I will use this in my own code sometime soon, and I think I'll use ArrayBegin, ArrayEnd, ArrayConstBegin, and ArrayConstEnd.

Mark Ransom
j_random_hacker
Benoît
@Benoit: That's just the rules of C/C++ syntax -- they require array bounds *after* the identifier name (which is implied here). In the same way, to declare an array of 10 ints called foo, you need to write "int foo[10];" not "int[10] foo;" -- even though the latter looks reasonable, it won't parse.
j_random_hacker
Right. Thanks ! :)
Benoît
There is also boost::begin(), and boost::end(). BTW, we can also return a std::reverse_iterator<T*> for rbegin() and rend() definitions.
Luc Hermitte
I would go for an utility namespace and simple names: array::size, array::begin, array::end, array::cbegin (or similar for constant versions)...
David Rodríguez - dribeas