Decimus
Decimus

Reputation:

Calculating size of an array

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

Upvotes: 14

Views: 14668

Answers (9)

David Stone
David Stone

Reputation: 28903

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

Upvotes: 0

Mark Ransom
Mark Ransom

Reputation: 308520

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.

Upvotes: 4

paxdiablo
paxdiablo

Reputation: 882426

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}.

Upvotes: 27

Beno&#238;t
Beno&#238;t

Reputation: 16994

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.

Upvotes: 10

i_am_jorf
i_am_jorf

Reputation: 54640

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.

Upvotes: 1

Jimmy J
Jimmy J

Reputation: 1985

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"

Upvotes: 2

Jonathan Leffler
Jonathan Leffler

Reputation: 754900

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

Upvotes: 4

ojblass
ojblass

Reputation: 21630

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.

Upvotes: 2

Marcel Guzman
Marcel Guzman

Reputation: 1672

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.

Upvotes: 12

Related Questions