C++

Arrays and Pointers

Last updated Nov 18, 2004.

One of the first things that C programmers learn is that arrays are essentially pointers in disguise. This idiosyncrasy (at least for programmers of other high-level programming languages) is maintained in C++ as well. Today, I explore the resemblance between pointers and arrays, show some subtle differences between the two. I'll show refactoring techniques for improving code safety with respect to arrays.

When Arrays Behave like Pointers...

Functions in C/C++ can't return or receive array arguments. The common workaround in C is to pass the address of an array and an additional argument indicating its size:

void fillArray(int [], size_t sz);
int myArr[100];
fillArray(myArr, sizeof(myArr));

You can use the subscript notation on a pointer, assuming that that pointer is originally an array that decayed into a pointer:

char arrc[2];
char p=&arr[0];
p[0]=0; //ok
void func(char * pc)
{
pc[0]='a';
}

In C++, you would normally use a vector instead of a built-in array:

void fillArray(vector<int> &);
vector <int> vi;
fillArray(vi);

Later on, I show a different C++ technique for safe and efficient array handling that doesn't incur the overhead of vector's dynamic memory allocation and deallocation.

...And When They Don't

Although arrays decay into pointers in certain contexts, they aren't really pointers. For example, if you try to assign to an array, you get a compilation error:

char arr[10];
char c;
arr=&c; //error

However, assigning an array's name to a pointer is well-formed:

char *p=arr; 

In this case, the rvalue arr decays into a temporary pointer to char, containing the address of the first element of the array. Therefore, this assignment expression is equivalent to the following:

char * p=&arr[0];

The fact that arrays decay into pointers shouldn't mislead you; the compiler treats every array as a distinct type. The type of an array consists of the elements' scalar type (e.g., char) as well as the array's size and its cv qualifications. Therefore, each of the following arrays have distinct types:

char arr1[10]; 
char arr2[11]; 
cout<<typeid(arr1).name()<<endl; //output: 'char [10]'
cout<<typeid(arr2).name()<<endl; //output: 'char [11]'

sizeof Expressions

Operator sizeof returns the size of an array in bytes. However, the ice here is thin. When the array decays into a pointer, sizeof returns the size of the pointer instead. For example:

int func(int [] a) 
{
 return sizeof (a); // returns the size of the pointer p
}
int main()
{
 int num[2];
 cout << sizeof (num); //output 8 (on most platforms)
 func(num);
}

Array Wrapping

As mentioned above, C++ programmers should use a vector instead of a built-array. This way, the inherent pitfalls of built-in arrays are avoided.

The problem is that in some situations, using a vector is undesirable or simply impossible; a vector object entails at least one constructor call and one destructor call. In addition, it's impossible to have vector allocate its storage statically, as opposed to built-in arrays.

As a workaround, you can wrap a built-in array in a high-level class without altering the array's low-level properties. The following array_wrapper class template wraps a built-in array in a lightweight struct that has no additional data members except for the array itself. Unlike ordinary STL containers, this wrapper class allocates its array on the stack, statically. More importantly, it provides the standard member functions of other STL containers. This way, it can be used with standard algorithms:

template <class T, int _size> struct array_wrapper
{
 //typedef names used in STL algorithms and containers
 typedef T value_type;
 typedef T* iterator;
 typedef const T * const_iterator;
 typedef T& reference;
 typedef const T& const_reference;

T v[_size]; //the actual array
// member functions of STL containers
operator T* () {return v;}
reference operator[] (size_t idx) {return v[idx];}
const_reference operator[] (size_t idx) const {return v[idx];}

iterator begin() {return v;}
const_iterator begin() const {return v;}

iterator end() {return v+_size;}
const_iterator end() const {return v+_size;}

size_t size() const {return _size;}
};

You can initialize an array_wrapper object as you would a built-in array. The resulting object can be used in C-style code that expects a built-in array:

void func(int *, int sz); // C function 
//wrap an array of 20 int's initializing its members to 0
array_wrapper<int, 20> arr={0}; 
func(arr, arr.size()); //using T* conversion operator

Such a wrapper is particularly useful for bridging between the Standard Library's containers, algorithms and iterators, and a built-in array. The following examples uses the copy() algorithm and ostream_iterator iterator class to display the contents of arr on the screen:

copy(arr.begin(), arr.end(),
 ostream_iterator<int>(cout, " ")); 

The array_wrapper class isn't included in the C++98 Standard Library. However, it is likely that a similar wrapper class template will be added to it in the next revision round.

Summary

The close resemblance between arrays and pointers dates back to the pre-C age with mid-level languages such as BCPL and B. In the early days of STL, some of the C++ standards committee members proposed that C's built-in arrays would cease to exist in C++. Instead, the compiler should convert under the hood every array into a vector object.

This proposal was rejected for several good reasons, such as performance and compatibility with C. However, if you find yourself using built-in arrays in new C++ code, you should probably consider refactoring. Even if replacing every array with a vector isn't a practical solution (and I admit that it isn't), the use of a wrapper class can be an acceptable compromise.