📄 index.html
字号:
<tt>}</tt>
</pre>
<h3> <a name="Heading35"> Detecting a Machine's Endian</a></h3>
<p>The term <i>endian</i> refers to the way in which a computer architecture stores
the bytes of a multibyte number in memory. When bytes at lower addresses have
lower significance (as is the case with Intel microprocessors, for instance),
it is called <i>little endian ordering</i>. Conversely, <i>big endian ordering</i>
describes a computer architecture in which the most significant byte has the
lowest memory address. The following program detects the endian of the machine
on which it is executed:</p>
<pre>
<tt>int main()</tt>
<tt>{</tt>
<tt> union probe</tt>
<tt> {</tt>
<tt> unsigned int num;</tt>
<tt> unsigned char bytes[sizeof(unsigned int)];</tt>
<tt> };</tt>
<tt> probe p = { 1U }; //initialize first member of p with unsigned 1</tt>
<tt> bool little_endian = (p.bytes[0] == 1U); //in a big endian architecture, </tt>
<tt> //p.bytes[0] equals 0</tt>
<tt> return 0;</tt>
<tt>}</tt>
</pre>
<h3> <a name="Heading36"> The Lifetime Of A Bound Temporary Object</a></h3>
<p>You can safely bind a reference to a temporary object. The temporary object
to which the reference is bound persists for the lifetime of the reference.
For example</p>
<pre>
<tt>class C</tt>
<tt>{</tt>
<tt>private:</tt>
<tt> int j;</tt>
<tt>public:</tt>
<tt> C(int i) : j(i) {}</tt>
<tt> int getVal() const {return j;}</tt>
<tt>};</tt>
<tt>int main()</tt>
<tt>{</tt>
<tt> const C& cr = C(2); //bind a reference to a temp; temp's destruction</tt>
<tt> //deferred to the end of the program</tt>
<tt> C c2 = cr; //use the bound reference safely</tt>
<tt> int val = cr.getVal();</tt>
<tt> return 0;</tt>
<tt>}//temporary destroyed here along with its bound reference</tt>
</pre>
<h3> <a name="Heading37"> Deleting A Pointer More Than Once</a></h3>
<p>The result of applying <tt>delete</tt> to the same pointer after it has been
deleted is undefined. Clearly, this bug should never happen. However, it can
be prevented by assigning a <tt>NULL</tt> value to a pointer right after it
has been deleted. It is guaranteed that a <tt>NULL</tt> pointer deletion is
harmless. For example</p>
<pre>
<tt>#include <string></tt>
<tt>using namespace std;</tt>
<tt>void func</tt>
<tt>{</tt>
<tt> string * ps = new string;</tt>
<tt> //...use ps</tt>
<tt> if ( ps->empty() )</tt>
<tt> {</tt>
<tt> delete ps;</tt>
<tt> ps = NULL; //safety-guard: further deletions of ps will be harmless</tt>
<tt> }</tt>
<tt> //...many lines of code</tt>
<tt> delete ps; // ps is deleted for the second time. Harmless however</tt>
<tt>}</tt>
</pre>
<h2> <a name="Heading38"> Data Pointers Versus Function Pointers</a></h2>
<p>Both C and C++ make a clear-cut distinction between two types of pointers --
data pointers and function pointers. A function pointer embodies several constituents,
such as the function's signature and return value. A data pointer, on the other
hand, merely holds the address of the first memory byte of a variable. The substantial
difference between the two led the C standardization committee to prohibit the
use of <tt>void*</tt> to represent function pointers, and vice versa. In C++,
this restriction was relaxed, but the results of coercing a function pointer
to a <tt>void*</tt> are implementation-defined. The opposite -- that is, converting
data pointers to function pointers -- is illegal.</p>
<h2> <a name="Heading39"> Pointer Equality</a></h2>
<p>Pointers to objects or functions of the same type are considered equal in three
cases:</p>
<ul>
<li>
<p> If both pointers are <tt>NULL</tt>. For example</p>
</li>
</ul>
<p></p>
<pre>
<tt>int *p1 = NULL, p2 = NULL;</tt>
<tt>bool equal = (p1==p2); //true</tt>
</pre>
<ul>
<li>
<p> If they point to the same object. For example</p>
</li>
</ul>
<p></p>
<pre>
<tt>char c;</tt>
<tt>char * pc1 = &c;</tt>
<tt>char * pc2 = &c;</tt>
<tt>bool equal = (pc1 == pc2); // true</tt>
</pre>
<ul>
<li>
<p> If they point one position past the end of the same array. For example</p>
</li>
</ul>
<p></p>
<pre>
<tt>int num[2];</tt>
<tt>int * p1 = num+2, *p2 = num+2;</tt>
<tt>bool equal = ( p1 == p2); //true</tt>
</pre>
<h2> <a name="Heading40"> Storage Reallocation</a></h2>
<p>In addition to <tt>malloc()</tt> and <tt>free()</tt>, C also provides the function
<tt>realloc()</tt> for changing the size of an existing buffer. C++ does not
have a corresponding reallocation operator. Adding operator <tt>renew</tt> to
C++ was one of the suggestions for language extension that was most frequently
sent to the standardization committee. Instead, there are two ways to readjust
the size of memory that is allocated on the free store. The first is very inelegant
and error prone. It consists of allocating a new buffer with an appropriate
size, copying the contents of the original buffer to it and, finally, deleting
the original buffer. For example</p>
<pre>
<tt>void reallocate</tt>
<tt>{</tt>
<tt> char * p new char [100];</tt>
<tt> //...fill p</tt>
<tt> char p2 = new char [200]; //allocate a larger buffer</tt>
<tt> for (int i = 0; i<100; i++) p2[i] = p[i]; //copy</tt>
<tt> delete [] p; //release original buffer</tt>
<tt>}</tt>
</pre>
<p>Obviously, this technique is inefficient and tedious. For objects that change
their size frequently, this is unacceptable. The preferable method is to use
the container classes of the Standard Template Library (STL). STL containers
are discussed in Chapter 10, "STL and Generic Programming."</p>
<h2> <a name="Heading41"> Local Static Variables</a></h2>
<p>By default, local static variables (not to be confused with static class members)
are initialized to binary zeros. Conceptually, they are created before the program's
outset and destroyed after the program's termination. However, like local variables,
they are accessible only from within the scope in which they are declared. These
properties make static variables useful for storing a function's state on recurrent
invocations because they retain their values from the previous call. For example</p>
<pre>
<tt>void MoveTo(int OffsetFromCurrentX, int OffsetFromCurrentY)</tt>
<tt>{</tt>
<tt> static int currX, currY; //zero initialized</tt>
<tt> currX += OffsetFromCurrentX;</tt>
<tt> currY += OffsetFromCurrentY;</tt>
<tt> PutPixel(currX, currY);</tt>
<tt>}</tt>
<tt>void DrawLine(int x, int y, int length)</tt>
<tt>{</tt>
<tt> for (int i=0; i<length; i++)</tt>
<tt> MoveTo(x++, y--);</tt>
<tt>}</tt>
</pre>
<p>However, when the need arises for storing a function's state, a better design
choice is to use a class. Class data members replace the static variables and
a member function replaces the global function. Local static variables in a
member function are of special concern: Every derived object that inherits such
a member function also refers to the same instance of the local static variables
of its base class. For example</p>
<pre>
<tt>class Base</tt>
<tt>{</tt>
<tt>public:</tt>
<tt> int countCalls()</tt>
<tt> {</tt>
<tt> static int cnt = 0; </tt>
<tt> return ++cnt;</tt>
<tt> }</tt>
<tt>};</tt>
<tt>class Derived1 : public Base { /*..*/};</tt>
<tt>class Derived2 : public Base { /*..*/};</tt>
<tt>// Base::countCalls(), Derived1::countCalls() and Derived2::countCalls</tt>
<tt>// hold a shared copy of cnt</tt>
<tt>int main()</tt>
<tt>{</tt>
<tt> Derived1 d1;</tt>
<tt> int d1Calls = d1.countCalls(); //d1Calls = 1</tt>
<tt> Derived2 d2;</tt>
<tt> int d2Calls = d2.countCalls(); //d2Calls = 2, not 1</tt>
<tt> return 0;</tt>
<tt>}</tt>
</pre>
<p>Static local variables in the member function <tt>countCalls</tt> can be used
to measure load balancing by counting the total number of invocations of that
member function, regardless of the actual object from which it was called. However,
it is obvious that the programmer's intention was to count the number of invocations
through <tt>Derived2</tt> exclusively. In order to achieve that, a static class
member can be used instead:</p>
<pre>
<tt>class Base</tt>
<tt>{</tt>
<tt>private:</tt>
<tt> static int i; </tt>
<tt>public:</tt>
<tt> virtual int countCalls() { return ++i; }</tt>
<tt>};</tt>
<tt>int Base::i;</tt>
<tt>class Derived1 : public Base</tt>
<tt>{</tt>
<tt>private:</tt>
<tt> static int i; //hides Base::i</tt>
<tt>public:</tt>
<tt> int countCalls() { return ++i; } //overrides Base:: countCalls()</tt>
<tt>};</tt>
<tt>int Derived1::i;</tt>
<tt>class Derived2 : public Base</tt>
<tt>{</tt>
<tt>private:</tt>
<tt> static int i; //hides Base::i and distinct from Derived1::i</tt>
<tt>public:</tt>
<tt> virtual int countCalls() { return ++i; }</tt>
<tt>};</tt>
<tt>int Derived2::i;</tt>
<tt>int main()</tt>
<tt>{</tt>
<tt> Derived1 d1;</tt>
<tt> Derived2 d2;</tt>
<tt> int d1Calls = d1.countCalls(); //d1Calls = 1</tt>
<tt> int d2Calls = d2.countCalls(); //d2Calls also = 1</tt>
<tt> return 0;</tt>
<tt>}</tt>
</pre>
<p>Static variables are problematic in a multithreaded environment because they
are shared and have to be accessed by means of a lock.</p>
<h2> <a name="Heading42"> Global Anonymous Unions</a></h2>
<p>An <i>anonymous union</i> (anonymous unions are discussed in Chapter 12, "Optimizing
Your Code") that is declared in a named namespace or in the global namespace
has to be explicitly declared <tt>static</tt>. For example</p>
<pre>
<tt>static union //anonymous union in global namespace</tt>
<tt>{</tt>
<tt> int num;</tt>
<tt> char *pc;</tt>
<tt>};</tt>
<tt>namespace NS</tt>
<tt>{</tt>
<tt> static union { double d; bool b;}; //anonymous union in a named namespace</tt>
<tt>}</tt>
<tt>int main()</tt>
<tt>{</tt>
<tt> NS::d = 0.0;</tt>
<tt> num = 5;</tt>
<tt> pc = "str";</tt>
<tt> return 0;</tt>
<tt>}</tt>
</pre>
<h2> <a name="Heading43"> The const and volatile Properties of an Object</a></h2>
<p>There are several phases that comprise the construction of an object, including
the construction of its base and embedded objects, the assignment of a <tt>this</tt>
pointer, the creation of the virtual table, and the invocation of the constructor's
body. The construction of a <cite>cv</cite><i>-qualified</i> (<tt>const</tt>
or <tt>volatile)</tt> object has an additional phase, which turns it into a
<tt>const</tt>/<tt>volatile</tt> object. The <tt>cv</tt> qualities are effected
after the object has been fully constructed.</p>
<h2> <a name="Heading44"> Conclusions</a></h2>
<p>The complex memory model of C++ enables maximal flexibility. The three types
of data storage -- automatic, static, and free store -- offer a level of control
that normally exist only in assembly languages.</p>
<p>The fundamental constructs of dynamic memory allocation are operators <tt>new</tt>
and <tt>delete</tt>. Each of these has no fewer than six different versions;
there are plain and array variants, each of which comes in three flavors: exception
throwing, exception free, and placement.</p>
<p>Many object-oriented programming languages have a built-in <i>garbage collector</i>,
which is an automatic memory manager that detects unreferenced objects and reclaims
their storage (see also Chapter 14, "Concluding Remarks and Future Directions,"
for a discussion on garbage collection). The reclaimed storage can then be used
to create new objects, thereby freeing the programmer from having to explicitly
release dynamically-allocated memory. Having an automatic garbage collector
is handy because it eliminates a large source of bugs, runtime crashes, and
memory leaks. However, garbage collection is not a panacea. It incurs additional
runtime overhead due to repeated compaction, reference counting, and memory
initialization operations, which are unacceptable in time-critical applications.
Furthermore, when garbage collection is used, destructors are not necessarily
invoked immediately when the lifetime of an object ends, but at an indeterminate
time afterward (when the garbage collector is sporadically invoked). For these
reasons, C++ does not provide a garbage collector. Nonetheless, there are techniques
to minimize -- and even eliminate -- the perils and drudgery of manual memory
management without the associated disadvantages of garbage collection. The easiest
way to ensure automatic memory allocation and deallocation is to use automatic
storage. For objects that have to grow and shrink dynamically, you can use STL
containers that automatically and optimally adjust their size. Finally, in order
to create an object that exists throughout the execution of a program, you can
declare it <tt>static</tt>. Nonetheless, dynamic memory allocation is sometimes
unavoidable. In such cases, <tt>auto_ptr</tt>(discussed in Chapters 6 and 11,
"Memory Management") simplifies the usage of dynamic memory. </p>
<p>Effective and bug-free usage of the diversity of C++ memory handling constructs
and concepts requires a high level of expertise and experience. It isn't an
exaggeration to say that most of the bugs in C/C++ programs are related to memory
management. However, this diversity also renders C++ a multipurpose, no compromise
programming language.</p>
<CENTER>
<P>
<HR>
<p><A HREF="/publishers/que/series/professional/0789720221/index.htm"><img src="/publishers/que/series/professional/0789720221/button/contents.gif" WIDTH="128"
HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR>
<BR>
<BR>
</p><p></P>
<P>© <A HREF="/publishers/que/series/professional/0789720221/copy.htm">Copyright 1999</A>, Macmillan Computer Publishing. All
rights reserved.</p>
</CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -