Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

3.8 Putting It All Together


A Money Bag

The following program combines many of the features from this chapter.


Listing 3.18 Enumerated types, const references

 // money.C - enumerated types and const references
 #include <iostream.h>

 enum coins { penny = 1,nickel = 5,dime = 10,qtr = 25,half = 50 };
 enum bills { dollar = 1,five = 5,ten = 10,twenty = 20,fifty = 50 };

 const int max = 5;

 struct money {             // money definition
  coins coin[max];
  bills bill[max];
 };

 int main()
 {
  void count(const money &);

  static money bag = {         // fill the bag with money
    { dime, qtr, nickel, penny, nickel },
    { fifty, ten, five, dollar, dollar}
  };

  count(bag);             // count the money
  return 0;
 }

 void count(const money & loot) {
  int paper = 0, silver = 0;

  for (int i = 0; i < max; i++) {
    paper += loot.bill[i];
    silver += loot.coin[i];
  }
  cout << "You've got " << paper << " dollars ";
  cout << "and " << silver << " cents." << endl;
 }
 $ money
 You've got 67 dollars and 46 cents.

This program counts money. We use enums to create coins and bills before we define a money type. The static initialization statement inside main() fills the money bag with coins and bills. We count the money with function count(), whose signature is const money &. The reference lets us pass the money bag efficiently, and const makes sure count() doesn't embezzle any money. Inside count(), a for loop counts the number of coins and bills separately. A cout statement displays the correct amount.

An Optimized Block Move Function

Suppose an application needs to move data from one memory block to another one as fast as possible. We could write a function in assembly language, but let's see if we can implement a fast, portable, C++ function instead. Rather than move single bytes between memory locations, we use a technique that generates block move instructions with most C++ compilers. We can't guarantee that our function will always produce efficient code, but performance should improve with compilers that generate block move instructions from this technique.

Our bmove() function knows nothing about objects, so we restrict it to memory blocks containing only built-in types (characters, integers, doubles, structures, etc.). The bmove() function has three arguments: a destination pointer address, a source pointer address, and an integer count. The function generates as many block moves as possible, but if there are any bytes "left over," we transfer them one byte at a time. To implement block moves as we copy data, we encapsulate structure assignments within bmove() (see "Structure Copy and Assignment" on page 113).

Here's the implementation of bmove() that transfers data blocks.

Listing 3.19 Function bmove() — block moves

 void *bmove(void *destptr, const void *srcptr, size_t count) {
  const int bsize = 256;      // tunable block size parameter
  typedef unsigned char byte;

  struct mblock {
    byte buf[bsize];       // block buffer
  };

  byte *dest = static_cast<byte *>(destptr);
  const byte *src = static_cast<const byte *>(srcptr);

  while (count >= bsize) {          // transfer blocks
    *reinterpret_cast<mblock *>(dest) =   // block move
     *reinterpret_cast<const mblock *>(src);
    dest += bsize;             // increment dest pointer
    src += bsize;              // increment src pointer
    count -= bsize;             // decrement count
  }

  while (count--)      // transfer remaining bytes
    *dest++ = *src++;
  return destptr;      // return pointer to data
 }

Void pointers in bmove()'s signature allow applications to call it with pointers to any built-in type. The const void * with srcptr prevents bmove() from modifying any data that srcptr points to. A typedef defines byte as an unsigned character, and the mblock structure implements a 256-byte buffer for the block moves. The static casts convert void pointers to byte pointers so that pointer arithmetic is possible with dest and src. Note that we must preserve const with src and srcptr to avoid compilation errors with static_cast.

The first while loop implements 256 (bsize) byte block transfers. The statement

 *reinterpret_cast<mblock *>(dest) =     // block move
  *reinterpret_cast<const mblock *>(src);

performs the block moves. The reinterpret casts are necessary to convert byte pointers to mblock pointers. When we apply the indirection operator (*) to these mblock pointers, the compiler performs structure assignment. The result of this operation moves 256 bytes in memory from src to dest. After the transfer, we increment the pointers by 256 and decrement count by the same amount. Again, note the use of const with mblock and src to avoid compilation errors with reinterpret_cast.

The function performs block move transfers as long as count is greater than or equal to bsize. The second while loop transfers the remaining bytes one at a time. The function returns a pointer to its source address (first argument).

Here is a sample program that calls bmove() for integer and character arrays.

Listing 3.20 - Integer and character block moves

 // bmove.c - optimized block move function
 #include <iostream.h>
 #include <iomanip.h>
 #include <stdlib.h>
 #include <memory.h>

 int main(int argc, const char *argv[])
 {
  if (argc != 2) {
    cerr << "Usage: " << argv[0] << " length" << endl;
    return 1;
  }

  int i, length = atoi(argv[1]);
  int *a = new int[length];      // destination array
  int *b = new int[length];      // source array

  for (i = 0; i < length; i++)        // fill with integers
   b[i] = i;

  bmove(a, b, length * sizeof(int));    // block move integers

  for (i = 0; i < length; i++)       // display moved data
    cout << setw(4) << a[i] << (((i+1) % 10) ? ' ' : '\n');
  cout << endl;

  char *c = new char[length];        // destination array
  char *d = new char[length];        // source array

  memset(d, 'x', length);          // fill with 'x' chars

  bmove(c, d, length);           // block move chars

  for (i = 0; i < length; i++)       // display moved data
    cout << setw(4) << c[i] << (((i+1) % 10) ? ' ' : '\n');
  delete [] a;   delete [] b;
  delete [] c;   delete [] d;
  return 0;
 }

 $ bmove 1200
    0   1   2   3   4   5   6   7   8   9
   10  11   12  13  14  15  16  17  18  19
  . . . . . . . . . . . . . . . . . . . . . . . . . .
  1180 1181 1182  1183 1184 1185 1186 1187 1188 1189
  1190 1191 1192  1193 1194 1195 1196 1197 1198 1199

    x   x   x   x   x   x   x   x   x   x
  . . . . . . . . . . . . . . . . . . . . . . . . . .
    x   x   x   x   x   x   x   x   x   x

The program creates source and destination arrays with operator new and a length from the command line. We fill both source arrays with data before calling bmove() to transfer data. The program displays 10 data elements on each line after the transfers.

On machines with 16-bit integers, bmove() performs 9 block moves (2,304 bytes) and 96 single-byte transfers with a length of 1200 (2,400 bytes). Likewise, bmove() performs 4 block moves (1,024 bytes) and 76 single-byte transfers for the 1200-byte character arrays. This means bmove() transfers 96 percent of its integer data and 85 percent of its character data with block moves for a length of 1200.

NOTE

We call the bsize constant in bmove() a tunable parameter. The performance of bmove() may vary, based on machine word size and the type of CPU you are using. You may want to experiment with different bsize constants to determine how bmove() performs on your machine.

  • + Share This
  • 🔖 Save To Your Account