- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
-
Tips and Techniques
- Using the Swap() Algorithm
- Using class stopwatch for Performance Measurements
- Extending <tt><iostream></tt> to Support User-Defined Types
- Using <tt>auto_ptr</tt> To Automate Memory Management
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part II
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part III
- Using <tt>enum</tt>s as Mnemonic Indexes
- Create Objects on Pre-Allocated Memory Using <tt>Placement-new</tt>
- Online Books: <tt>Placement-new</tt>
- Bitwise Operators
- Bitwise Operators II
- Who's <tt>this</tt>?
- A Reference Guide
- The Virtues of Multiple Inheritance
- Interfaces
- Multiple Inheritance: Construction and Destruction Order
- nothrow new
- POD Initialization
- Object Initialization
- <tt>const</tt> Declarations
- The Semantics of <tt>volatile</tt>
- <tt>inline</tt> Functions
- Project Organization Guidelines
- All About <tt>bool</tt>
- <tt>typedef</tt> Declarations
- State of the <tt>union</tt>
- Dynamic Cast Uses
- Integrating C and C++
- <tt>const</tt>-Correctness
- <tt>const</tt>-Correctness: Advanced Issues
- Sprucing Up Legacy Code
- Virtual Constructors
- Naming Names
- Function Calls
- Speaking Standardese (updated)
- Speaking Standardese: the One Definition Rule
- Declarations and Definitions
- More on Declarations and Definitions
- The Most Vexing Parse
- Finally, At Last
- Sound Bytes (Admittedly Off Topic)
- Local Classes
- Complex Arithmetic
- Floating Point Woes
- String Manipulation
- The Object Model
- The Object Model II
- The Object Model III
- Temporary Objects
- Temporary Objects: Advanced Techniques
- Over-Engineering
- Security Enhancements
- Drop the (automatic) Pilot
- Choosing the Right Container
- Choosing the Right Container II
- Choosing the Right Container, Part III
- Arrays and Pointers
- Low-Level File I/O
- Low-Level File I/O Part II
- static Declarations, Part I
- static Declarations, Part II
- <code>static</code> Initialization Order
- Revisiting the Deprecation of File-Scope Static
- Virtual Memory and Memory Mapping
- Cellular Phone Programming Guidelines
- The Handle/Body Idiom
- Whole Program Optimization, Part I
- Whole Program Optimization, Part II
- Manipulating Directories
- Window Dressing
- <code>friend</code> Declarations
- <code>friend</code> Part II: the Interaction of Friendship and Template Classes
- Forcing Object Allocation on Specific Storage Types
- Lazy Evaluation
- Cache and Carry
- Controlling a Container’s Capacity
- Non-Blocking I/O, Part I
- Non-Blocking I/O, Part II
- Using Unions for Automatic Conversion
- Launching a Child Process
- <tt>switch</tt> Statements
- Introducing the "struct Hack"
- Scoped Enumerators
- Doing Statistics with STL
- Fixing the "Unresolved External" Linkage Error
- Understanding Calling Conventions
- Understanding the Empty Base Optimization
- Implementing RPC with the door Library, Part 1
- Implementing RPC with the door Library, Part 2
- Eliminating Two Common Pointer and <tt>sizeof</tt> Bugs
- Command Line Arguments
- Performance Myths Busting
- Tag Names And Types Part I
- Tag Names And Types Part II
- The Infamous goto
- Trimming Strings
- Can Objects Live Forever? Part I
- Can Objects Live Forever? Part II
- Five Ways to Improve Your Functions
- Member Aggregate Initialization
- Five Futile Coding-Style Debates
- The Good Parasite Idiom: An Exercise in OOD
- The Good Parasite Idiom: An Exercise in OOD, Part II
- The Good Parasite Idiom: An Exercise in OOD, Part III
- Ten Techniques to Reduce the Size of Your Classes, Part I
- Ten Techniques to Reduce the Size of Your Classes, Part II: Inheritance Issues
- Ten Techniques to Reduce the Size of Your Classes, Part III
- Ten Techniques to Reduce the Size of Your Classes IV
- Taking the Address of an Object with an Overloaded Operator <tt>&</tt>
- strcpy() -- How and Why Does It "Just Work"?
- Anonymous Structs
- Five Easy Ways to Reduce The Size of your Executables
- Standard Layout Classes and Trivially Copyable Types, Part I
- Standard Layout Classes and Trivially Copyable Types, Part II
- Five Simple Code Sanity Checks
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
Arrays and Pointers
Last updated Jan 1, 2003.
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.
