13

How do I reliably get the size of a C-style array? The method often recommended seems to be to use sizeof, but it doesn't work in the foo function, where x is passed in:

#include <iostream>

void foo(int x[]) {
  std::cerr << (sizeof(x) / sizeof(int)); // 2  
}

int main(){
    int x[] = {1,2,3,4,5};
    std::cerr << (sizeof(x) / sizeof(int)); // 5                              
    foo(x);
    return 0;
}

Answers to this question recommend sizeof but they don't say that it (apparently?) doesn't work if you pass the array around. So, do I have to use a sentinel instead? (I don't think the users of my foo function can always be trusted to put a sentinel at the end. Of course, I could use std::vector, but then I don't get the nice shorthand syntax {1,2,3,4,5}.)

Community
  • 1
  • 1
Frank
  • 64,140
  • 93
  • 237
  • 324
  • 1
    http://stackoverflow.com/questions/1810083/c-pointers-pointing-to-an-array-of-fixed-size – AnT stands with Russia Mar 08 '10 at 20:44
  • If the users of your function don't do what the docs tell them to do, that is their problem not yours. It is impossible to prevent the clueless from misusing an API. –  Mar 08 '10 at 20:52
  • `x` in `foo` is a pointer, rather than an array. See http://stackoverflow.com/questions/232691/how-can-i-get-the-size-of-an-array-from-a-pointer-in-c, http://stackoverflow.com/questions/1975128/sizeof-an-array-in-the-c-programming-language. Also, dup: http://stackoverflow.com/questions/37538/c-how-do-i-determine-the-size-of-my-array – outis Mar 08 '10 at 20:53
  • @Neil Butterworth: that's what the `private` modifier does. – bta Mar 08 '10 at 21:35

10 Answers10

17

In C array parameters in C are really just pointers so sizeof() won't work. You either need to pass in the size as another parameter or use a sentinel - whichever is most appropriate for your design.

Some other options:

Some other info:

  • for C++, instead of passing a raw array pointer, you might want to have the parameter use something that wraps the array in a class template that keeps track of the array size and provides methods to copy data into the array in a safe manner. Something like STLSoft's array_proxy template or Boost's boost::array might help. I've used an array_proxy template to nice effect before. Inside the function using the parameter, you get std::vector like operations, but the caller of the function can be using a simple C array. There's no copying of the array - the array_proxy template takes care of packaging the array pointer and the array's size nearly automatically.

  • a macro to use in C for determining the number of elements in an array (for when sizeof() might help - ie., you're not dealing with a simple pointer): Is there a standard function in C that would return the length of an array?

Community
  • 1
  • 1
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
6

A common idiom mentioned in GNU Libstdc++ documentation is the lengthof function:

template<typename T, unsigned int sz>
inline unsigned int lengthof(T (&)[sz]) { return sz; }

You can use it as

int x[] = {1,2,3,4,5};
std::cerr << lengthof(x) << std::endl;

Warning: this will work only when the array has not decayed into a pointer.

Danilo Piazzalunga
  • 7,590
  • 5
  • 49
  • 75
3

You can either pass the size around, use a sentinel or even better use std::vector. Even though std::vector lacks initializer lists it is still easy to construct a vector with a set of elements (although not quite as nice)

static const int arr[] = {1,2,3,4,5};
vector<int> vec (arr, arr + sizeof(arr) / sizeof(arr[0]) );

The std::vector class also makes making mistakes far harder, which is worth its weight in gold. Another bonus is that all C++ should be familiar with it and most C++ applications should be using a std::vector rather than a raw C array.

As a quick note, C++0x adds Initializer lists

std::vector<int> v = {1, 2, 3, 4};

You can also use Boost.Assign to do the same thing although the syntax is a bit more convoluted.

std::vector<int> v = boost::assign::list_of(1)(2)(3)(4);

or

std::vector<int> v;
v += 1, 2, 3, 4;
Yacoby
  • 54,544
  • 15
  • 116
  • 120
3

How about this?..

template <int N>
void foo(int (&x)[N]) {
    std::cerr << N;
}
Corwin
  • 575
  • 2
  • 9
  • 3
    @Brian: It may or may not be a bad idea, depending on the context. Why do you think it is horrible? It is a good idea because it will NOT COMPILE if you pass a pointer in. It is bad if the function is big and called with many different array sizes - it will generate a bunch of functions. An alternative might be for this foo to then turn around and call a private/internal foo_internal(int *x, size_t length); It depends on use. I've found this technique useful with string functions which tend to otherwise overflow if improperly used. A comment without explanation is a horrible idea. – tony Mar 08 '10 at 21:32
2

I also agree that Corwin's method above is very good.

template <int N>
void foo(int (&x)[N]) 
{
    std::cerr << N;
}

I don't think anybody gave a really good reason why this is not a good idea.
In java, for example, we can write things like:

int numbers [] = {1, 2, 3, 4};
for(int i = 0; i < numbers.length(); i++)
{
   System.out.println(numbers[i]+"\n");
}

In C++ it would be nice instead of saying

int numbers [] = {1, 2, 3, 4};
int size = sizeof(numbers)/sizeof(int);
for(int i = 0; i < size; i++)
{
    cout << numbers[i] << endl;
}

We could take it a step further and go

template <int N>
int size(int (&X)[N])
{
   return N;
}

Or if that causes problems I suppose you could write explicitly:

template < int N >
int size(int (&X)[N])
{
   int value = (sizeof(X)/sizeof(X[0]));
   return value;
}

Then we just have to go in main:

int numbers [] = {1, 2, 3, 4};
for(int i = 0; i < size(numbers); i++)
{
   cout << numbers[i] << endl;
}

makes sense to me :-)

Spencer
  • 21
  • 3
2

c provides no native support for this. Once an array is passed out of its declared scope, its size is lost.

You can pass the size with the array. You can even bundle them into a structure if you always to to keep the size, though you'll have some bookkeepping overhead with that.

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
1

An array expression will have its type implicitly converted from "N-element array of T" to "pointer to T" and its value will be the address of the first element in the array, unless the array expression is the operand of either the sizeof or address-of (&) operators, or if the array expression is a string literal being used to initialize another array in a declaration. In short, you can't pass an array to a function as an array; what the function receives is a pointer value, not an array value.

You have to pass the array size as a separate parameter.

Since you're using C++, use vectors (or some other suitable STL container) instead of C-style arrays. Yes, you lose the handy shorthand syntax, but the tradeoff is more than worth it. Seriously.

John Bode
  • 119,563
  • 19
  • 122
  • 198
1

Now, you can use C++11's extent and rank.

By example:

#include <iostream>
#include <type_traits>

int main()
{
  int a[][3] = {{1, 2, 3}, {4, 5, 6}};

  std::cout << "\nRank:           : " << std::rank<decltype(a)>::value;
  std::cout << "\nSize: [_here_][]: " << std::extent<decltype(a), 0>::value;
  std::cout << "\nSize: [][_here_]: " << std::extent<decltype(a), 1>::value;
  std::cout << "\nSize: [][]_here_: " << std::extent<decltype(a), 2>::value;
}

prints:

Rank:           : 2
Size: [_here_][]: 2
Size: [][_here_]: 3
Size: [][]_here_: 0
Picaud Vincent
  • 10,518
  • 5
  • 31
  • 70
0

You need to pass the size along with the array, just like it is done in many library functions, for instance strncpy(), strncmp() etc. Sorry, this is just the way it works in C:-).

Alternatively you could roll out your own structure like:

struct array {
    int* data;
    int size;
};

and pass it around your code.

Of course you can still use std::list or std::vector if you want to be more C++ -ish.

pajton
  • 15,828
  • 8
  • 54
  • 65
-1

Since c++11, there is a very convenient way:

static const int array[] = { 1, 2, 3, 6 };
int size = (int)std::distance(std::begin(array), std::end(array))+1;
galinette
  • 8,896
  • 2
  • 36
  • 87