📄 ch11.htm
字号:
<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. Allrights reserved.</p></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -