| Version 1.9 Build 1556
|
|
Up: AIPS++ Programming Standards DRAFT Version
Previous: Guidelines
Subsections
Concerning the sizes of the standard integral types the following are
guaranteed 9:
- 1 == sizeof(char) <= sizeof(int) <= sizeof(long)
- sizeof(float) <= sizeof(double) <= sizeof(longdouble)
- sizeof(t) == sizeof(signed t) == sizeof(unsigned t) where t is one of the basic types
Additionally, the following are the minimum sizes guaranteed for the integral
types 10 and the floating point
types 11:
- char -- 8 bits
- short -- 16 bits
- long -- 32 bits
- float -- 32 bits
- double -- 64 bits
- long double -- 96 bits
The size of an int is typically 16 to 32 bits, but this size is not
guaranteed (32 bits in all probability for machines running AIPS++). The
size of the integral and floating point types are available in the header
files <limits.h> and <float.h> respectively.
(see p.72 Plum)
In addition, dependencies on the byte ordering of numeric types should be
avoided. For example, the following is a bad idea:
short i = 25;
char *cptr;
cptr = (char *) \&i;
One cannot anticipate the byte to which cptr points, given the possible
difference in architecture byte orderings (p.74 Plum). In addition, when
passing integral values between machines, the values should be converted
to/from network byte ordering via the routines in <netinet/in.h>,
e.g. htonl, etc.
Another possible source of alignment errors, is hard-coding offsets into
structures because the packing of structure members into the storage
is implementation dependent. The proper way to determine the offset
is to use the offsetof macro in <stddef.h>(see p. 84 Plum).
So for a struct like:
struct mystruct \{
int x;
int y;
\};
offsetof(mystruct, y) might yield 4.
Character constants should contain only one character because differences
in machine byte order may lead to values which differ in numeric value of
character sequence, for example:
short crlf = '\r\n';
is a non-portable use of character constants. A literal string can be used
if appropriate, or a left shift can be used to portably generate a
integral value out of characters, e.g.
\#define CHAR2(a, b) (((a) << CHAR\_BIT) + (b))
portably combines two characters 12.
String literals could also be a source of problems. They should not be
modified instead named arrays should be used. In the following example,
the first method is the portable way to obtain a modifiable string; the
second is the wrong way 13:
- 1.
- static char fname[] = "/tmp/edXXXXXX"; mktemp(fname);
- 2.
- mktemp("/tmp/edXXXXXX")
It is important to remember that global static objects which have a constructor
or are initialized with a constant expression are initialized in the order
in which they are encountered 14. There are ways to ensure
a certain initialization order 15.
Another possible source of problems is with the initialization
of member variables in a constructor definition. The member variables are
initialized in the order in which they occur in the class, regardless of their
order in the constructor definition. So for example:
class A\{
private:
int x;
int y;
int z;
public:
A() : z(12), y(9), x(z+y) \{\};
A(int t) : x(8), y(t), z(0) \{\};
\};
will cause problems, because its order of initialization will always be --
x, y, then z. This choice was made to have consistent initialization orders.
Otherwise, if the initialization order was dependent upon the order in the
constructor definition, the two constructors in this example would have
different initialization orders 16.
In general, it is best to avoid designs which depend on the initialization
order of static globals or static members. Also, it is important to remember
the initialization order of member variables to avoid problems.
When deleting an array of objects, it is important to call the delete
operation with the array specifier. Otherwise, the destructors for each of the
elements in the array will not be called. Only the destructor for the first
one will be called. So for example, if we have a string class which allocates
memory to hold the string:
class String\{
private:
char *rep;
public:
String(char *);
String()\{ rep = 0;\}
const char *operator*()\{ return rep;\}
String \&operator=(char *);
~String()\{ delete rep;\}
\};
String::String(char *v)\{
if (v != 0)\{
rep = new char[strlen(v)+1];
strcpy(rep,v);
\} else
rep = 0;
\}
String \&String::operator=(char *newval)\{
if (rep != 0)
delete rep;
rep = new char[strlen(newval)+1];
strcpy(rep,newval);
return(*this);
\}
main() \{
String *s = new String[3];
s[0] = "Hello";
s[1] = "there";
s[2] = "friend";
delete s;
\}
the call to delete s only causes the destructor for the first
s[0] to be called. The space allocated for there and
friend is lost forever. The correct way to delete s is
delete [] s 17.
Another interesting point is that for an array of objects to be allocated
via new a default constructor has to be defined, i.e. a
constructor without parameters 18. Specifying a constructor
with all default parameters is not sufficient.
Sometimes it is important for destructors to be declared as virtual in a
base class. This is important when all of the following hold
19:
- There are classes derived from the base class.
- The destructor in the derived class is different form the base class
destructor.
- Derived class objects may be deleted via a pointer or reference to base
class objects.
It is important for the destructor to be virtual when these conditions hold
because when deleting a derived object via a pointer to the base class,
only the destructor for the base class will be called. The derived class'
destructor will not be called, thus possibly causing memory leaks.
Another cause of memory problems is references. It is generally a bad idea to
initialize a reference to memory which can be deleted, e.g. free store,
automatic variables. Typically this problem will result in segmentation
violations or the like, and not in memory leaks. However, a substantial amount
of time could be expended tracking down the source of error (see p.38 Plum).
Some problems can arise with classes which define the operators new and
delete with more than one argument because these definitions hide
the global or base class definitions. For example 20:
typedef void (*PEHF)();
class X \{
public:
void *operator new(size\_t, PEHF pehf);
\}
main()\{
void specialErrorHandler();
X *px1 = new (specialErrorHandler) X;
X *px2 = new X;
\}
The first new succeeds with no problems. The second however causes an
error because the global new which takes one parameter, size_t,
is hidden by the local new in class X. This problem can be
corrected by either calling the global new explicitly, X *px2 = ::new X,
or by providing a new operator in X which takes only one
parameter. It is also a good idea to supply both new and
delete if one is needed to avoid future maintenance problems.
Be aware that if you don't explicitly disallow the copy operator,
Type::operator=(Type&), one will be supplied for
you, i.e. a bit copy. One way to ensure that there is no copy operator
is to make its declaration private, preventing users from accessing it, and not
providing a definition, preventing friends from using it.
Up: AIPS++ Programming Standards DRAFT Version
Previous: Guidelines
Please send questions or comments about AIPS++ to aips2-request@nrao.edu.
Copyright © 1995-2000 Associated Universities Inc.,
Washington, D.C.
Return to AIPS++ Home Page
2006-10-15