13

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.

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

Output:

arr : bffffa40
sizeof arr: 4
arr : bffffa40
sizeof arr: 1
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
  • Calling the foo parameter the same name is making this counterintuitive to you. Consider giving it another name simulate what the compiler is looking at. – ojblass Apr 06 '09 at 02:29

9 Answers9

27

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 tricky bits in C/C++ - the use of arrays and pointers are not always identical. Arrays will, under a great many circumstances, decay to a pointer to the first element of that array.

There are at least two solutions, compatible with both C and C++:

  • pass the length in with the array (not that useful if the intent of the function is to actually work out the array size).
  • pass a sentinel value marking the end of the data, e.g., {1,2,3,4,-1}.
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
12

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
  • 1,672
  • 1
  • 10
  • 6
10

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
  • 16,798
  • 8
  • 46
  • 66
4

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
  • 730,956
  • 141
  • 904
  • 1,278
  • +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 Apr 06 '09 at 05:41
  • Yeah, I was just wondering about sizeof(complete-array-type parameters) myself. – aib Apr 06 '09 at 23:06
4

Edit: C++11 was introduced since this answer was written, and it includes functions to do exactly what I show below: std::begin and std::end. Const versions std::cbegin and std::cend are also going into a future version of the standard (C++14?) and may be in your compiler already. Don't even consider using my functions below if you have access to the standard functions.


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!

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.

Community
  • 1
  • 1
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • +1, good idea, but please change "T[N]& array" (which is not a valid C++ type, and won't compile) to "T (&array)[N]". – j_random_hacker Apr 06 '09 at 06:23
  • @j_random_hacker : Can you explain why T[N]& is not a valid C++ type ? – Benoît Apr 06 '09 at 06:36
  • @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 Apr 06 '09 at 07:20
  • There is also boost::begin(), and boost::end(). BTW, we can also return a std::reverse_iterator for rbegin() and rend() definitions. – Luc Hermitte Apr 06 '09 at 17:54
  • 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 Apr 06 '09 at 18:35
2

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
  • 21,146
  • 22
  • 83
  • 132
2
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
  • 1,953
  • 1
  • 14
  • 20
1

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.

i_am_jorf
  • 53,608
  • 15
  • 131
  • 222
0

Now that we have constexpr in C++11, the type safe (non-macro) version can also be used in a constant expression.

template<typename T, std::size_t size>
constexpr std::size_t array_size(T const (&)[size]) { return size; }

This will fail to compile where it does not work properly, unlike your macro solution (it won't work on pointers by accident). You can use it where a compile-time constant is required:

int new_array[array_size(some_other_array)];

That being said, you are better off using std::array for this if possible. Pay no attention to the people who say to use std::vector because it is better. std::vector is a different data structure with different strengths. std::array has no overhead compared to a C-style array, but unlike the C-style array it will not decay to a pointer at the slightest provocation. std::vector, on the other hand, requires all accesses to be indirect accesses (go through a pointer) and using it requires dynamic allocation. One thing to keep in mind if you are used to using C-style arrays is to be sure to pass std::array to a function like this:

void f(std::array<int, 100> const & array);

If you do not pass by reference, the data is copied. This follows the behavior of most well-designed types, but is different from C-style arrays when passed to a function (it's more like the behavior of a C-style array inside of a struct).

David Stone
  • 26,872
  • 14
  • 68
  • 84