📄 ch11.htm
字号:
<tt> int age; // int occupies 4 bytes</tt><tt> char lastName[8];</tt><tt>}; //the actual size of Person is most likely larger than 17 bytes</tt><tt>void func()</tt><tt>{</tt><tt> Person person = {{"john"}, 30, {"lippman"}};</tt><tt> memset(&person, 0, 5+4+8 ); //may not erase the contents of</tt><tt> //person properly</tt><tt>}</tt></pre><p>On a 32-bit architecture, three additional bytes can be inserted between the first and the second members of <tt>Person</tt>, increasing the size of <tt>Person</tt> from 17 bytes to 20. </p><p>On some implementations, the <tt>memset()</tt> call does not clear the last three bytes of the member <tt>lastName</tt>. Therefore, use the <tt>sizeof</tt> operator to calculate the correct size:</p><pre><tt>memset(&p, 0, sizeof(Person)); </tt></pre><h2> <a name="Heading24"> The Size Of A Complete Object Can Never Be Zero</a></h2><p>An empty class doesn't have any data members or member functions. Therefore, the size of an instance is seemingly zero. However, C++ guarantees that the size of a complete object is never zero. Consider the following example:</p><pre><tt>class Empty {};</tt><tt>Empty e; // e occupies at least 1 byte of memory</tt></pre><p>If an object is allowed to occupy zero bytes of storage, its address can overlap with the address of a different object. The most obvious case is an array of empty objects whose elements all have an identical address. To guarantee that a complete object always has a distinct memory address, a complete object occupies at least one byte of memory. Non-complete objects -- for example, base class subobjects in a derived class -- can occupy zero bytes of memory.</p><h2> <a name="Heading25"> User-Defined Versions of new and delete Cannot Be Declared in a Namespace</a></h2><p>User-defined versions of <tt>new</tt> and <tt>delete</tt> can be declared in a class scope. However, it is illegal to declare them in a namespace. To see why, consider the following example:</p><pre><tt>char *pc;</tt><tt>namespace A</tt><tt>{</tt><tt> void* operator new ( size_t );</tt><tt> void operator delete ( void * );</tt><tt> void func ()</tt><tt> {</tt><tt> pc = new char ( 'a');</tt><tt> }</tt><tt>}</tt><tt>void f() { delete pc; } // A::delete or ::delete?</tt></pre><p>Declaring <tt>new</tt> and <tt>delete</tt> in namespace <tt>A</tt> is confusing for both compilers and human readers. Some programmers might expect the operator <tt>A::delete</tt> to be selected in the function <tt>f()</tt> because it matches the operator <tt>new</tt> that was used to allocate the storage. In contrast, others might expect <tt>delete</tt> to be called because <tt>A::delete</tt> is not visible in <tt>f()</tt>. For this reason, the Standardization committee decided to disallow declarations of <tt>new</tt> and <tt>delete</tt> in a namespace.</p><h2> <a name="Heading26"> Overloading new and delete in a Class</a></h2><p>It is possible to override <tt>new</tt> and <tt>delete</tt> and define a specialized form for them for a given class. Thus, for a class <tt>C</tt> that defines these operators, the following statements</p><pre><tt>C* p = new C;</tt><tt>delete p;</tt></pre><p>invoke the class's versions of <tt>new</tt> and <tt>delete</tt>, respectively. Defining class-specific versions of <tt>new</tt> and <tt>delete</tt> is useful when the default memory management scheme is unsuitable. This technique is also used in applications that have a custom memory pool. In the following example, operator <tt>new</tt> for class <tt>C</tt> is redefined to alter the default behavior in case of an allocation failure; instead of throwing <tt>std::bad_alloc</tt>, this specific version throws a <tt>const char *</tt>. A matching operator <tt>delete</tt> is redefined accordingly:</p><pre><tt>#include <cstdlib> // malloc() and free()</tt><tt>#include <iostream></tt><tt>using namespace std;</tt><tt>class C</tt><tt>{</tt><tt>private:</tt><tt> int j;</tt><tt>public:</tt><tt> C() : j(0) { cout<< "constructed"<<endl; }</tt><tt> ~C() { cout<<"destroyed";}</tt><tt> void* operator new (size_t size); //implicitly declared static</tt><tt> void operator delete (void *p); //implicitly declared static</tt><tt>};</tt><tt>void* C::operator new (size_t size) throw (const char *)</tt><tt>{</tt><tt> void * p = malloc(size);</tt><tt> if (p == 0)</tt><tt> throw "allocation failure"; //instead of std::bad_alloc</tt><tt> return p;</tt><tt>}</tt><tt>void C::operator delete (void *p)</tt><tt>{</tt><tt> free(p); </tt><tt>}</tt><tt>int main()</tt><tt>{</tt><tt> try</tt><tt> {</tt><tt> C *p = new C;</tt><tt> delete p;</tt><tt> }</tt><tt> catch (const char * err)</tt><tt> {</tt><tt> cout<<err<<endl;</tt><tt> }</tt><tt> return 0;</tt><tt>}</tt></pre><p>Remember that overloaded <tt>new</tt> and <tt>delete</tt> are implicitly declared as static members of their class if they are not explicitly declared static. Note also that a user-defined <tt>new</tt> implicitly invokes the objects's constructor; likewise, a user-defined <tt>delete</tt> implicitly invokes the object's destructor. </p><h2> <a name="Heading27"> Guidelines for Effective Memory Usage</a></h2><p>Choosing the correct type of storage for an object is a critical implementation decision because each type of storage has different implications for the program's performance, reliability, and maintenance. This section tells you how to choose the correct type of storage for an object and thus avoid common pitfalls and performance penalties. This section also discusses general topics that are associated with the memory model of C++, and it compares C++ to other languages.</p><h3> <a name="Heading28"> Prefer Automatic Storage to Free Store Whenever Possible</a></h3><p>Creating objects on the free store, when compared to automatic storage, is more expensive in terms of performance for several reasons:</p><ul> <li> <p> <b>Runtime overhead</b> Allocating memory from the free store involves negotiations with the operating system. When the free store is fragmented, finding a contiguous block of memory can take even longer. In addition, the exception handling support in the case of allocation failures adds additional runtime overhead.</p> </li> <p></p> <li> <p> <b>Maintenance</b> Dynamic allocation might fail; additional code is required to handle such exceptions.</p> </li> <p></p> <li> <p> <b>Safety</b> An object might be accidentally deleted more than once, or it might not be deleted at all. Both of these are a fertile source of bugs and runtime crashes in many applications.</p> </li></ul><p></p><p>The following code sample demonstrates two common bugs that are associated with allocating objects on the free store:</p><pre><tt>#include <string></tt><tt>using namespace std;</tt><tt>void f()</tt><tt>{</tt><tt> string *p = new string;</tt><tt> //...use p</tt><tt> if (p->empty()!= false)</tt><tt> {</tt><tt> //...do something</tt><tt> return; //OOPS! memory leak: p was not deleted</tt><tt> }</tt><tt> else //string is empty</tt><tt> {</tt><tt> delete p;</tt><tt> //..do other stuff</tt><tt> }</tt><tt> delete p; //OOPS! p is deleted twice if isEmpty == false</tt><tt>}</tt></pre><p>Such bugs are quite common in large programs that frequently allocate objects on the free store. Often, it is possible to create objects on the stack, thereby simplifying the structure of the program and eliminating the potential for such bugs. Consider how the use of a local <tt>string</tt> object simplifies the preceding code sample:</p><pre><tt>#include <string></tt><tt>using namespace std;</tt><tt>void f()</tt><tt>{</tt><tt> string s;</tt><tt> //...use s</tt><tt> if (s.empty()!= false)</tt><tt> {</tt><tt> //...do something</tt><tt> return;</tt><tt> }</tt><tt> else</tt><tt> {</tt><tt> //..do other stuff</tt><tt> }</tt><tt>}</tt></pre><p>As a rule, automatic and static storage types are always preferable to free store.</p><h3> <a name="Heading29"> Correct Syntax for Local Object Instantiation</a></h3><p>The correct syntax for instantiating a local object by invoking its default constructor is</p><pre><tt>string str; //no parentheses</tt></pre><p>Although empty parentheses can be used after the class name, as in</p><pre><tt>string str(); //entirely different meaning</tt></pre><p>the statement has an entirely different meaning. It is parsed as a declaration of a function named <tt>str</tt>, which takes no arguments and returns a <tt>string</tt> by value.</p><h3> <a name="Heading30"> Zero As A Universal Initializer</a></h3><p>The literal <tt>0</tt> is an <tt>int</tt>. However, it can be used as a universal initializer for every fundamental data type. Zero is a special case in this respect because the compiler examines its context to determine its type. For example:</p><pre><tt> void *p = 0; //zero is implicitly converted to void *</tt><tt> float salary = 0; // 0 is cast to a float</tt><tt> char name[10] = {0}; // 0 cast to a '\0'</tt><tt> bool b = 0; // 0 cast to false</tt><tt> void (*pf)(int) = 0; // pointer to a function</tt><tt> int (C::*pm) () = 0; //pointer to a class member</tt></pre><h3> <a name="Heading31"> Always Initialize Pointers</a></h3><p>An uninitialized pointer has an indeterminate value. Such a pointer is often called a <i>wild pointer</i>. It is almost impossible to test whether a wild pointer is valid, especially if it is passed as an argument to a function (which in turn can only verify that it is not <tt>NULL</tt>). For example</p><pre><tt>void func(char *p );</tt><tt>int main()</tt><tt>{</tt><tt> char * p; //dangerous: uninitialized</tt><tt> //...many lines of code; p left uninitialized by mistake</tt><tt> if (p)//erroneously assuming that a non-null value indicates a valid address</tt><tt> {</tt><tt> func(p); // func has no way of knowing whether p has a valid address</tt><tt> }</tt><tt> return 0;</tt><tt>}</tt></pre><p>Even if your compiler does initialize pointers automatically, it is best to initialize them explicitly to ensure code readability and portability.</p><h2> <a name="Heading32"> Explicit Initializations of POD Object</a></h2><p>As was previously noted, POD objects with automatic storage have an indeterminate value by default in order to avoid the performance penalty incurred by initialization. However, you can initialize automatic POD objects explicitly when necessary. The following sections explain how this is done.</p><h3> <a name="Heading33"> Initializing Local Automatic Structs and Arrays</a></h3><p>One way to initialize automatic POD objects is by calling <tt>memset()</tt> or a similar initialization function. However, there is a much simpler way to do it -- without calling a function, as you can see in the following example: </p><pre><tt>struct Person</tt><tt>{</tt><tt> long ID;</tt><tt> int bankAccount;</tt><tt> bool retired;</tt><tt>};</tt><tt>int main()</tt><tt>{</tt><tt> Person person ={0}; //ensures that all members of</tt><tt> //person are initialized to binary zeros</tt><tt> return 0;</tt><tt>}</tt></pre><p>This technique is applicable to every POD struct. It relies on the fact that the first member is a fundamental data type. The initializer zero is automatically cast to the appropriate fundamental type. It is guaranteed that whenever the initialization list contains fewer initializers than the number of members, the rest of the members are initialized to binary zeros as well. Note that even if the definition of <tt>Person</tt> changes -- additional members are added to it or the members' ordering is swapped -- all its members are still initialized. The same initialization technique is also applicable to local automatic arrays of fundamental types as well as to arrays of POD objects :</p><pre><tt>void f()</tt><tt>{</tt><tt> char name[100] = {0}; //all array elements are initialized to '\0'</tt><tt> float farr[100] = {0}; //all array elements are initialized to 0.0</tt><tt> int iarr[100] = {0}; //all array elements are initialized to 0</tt><tt> void *pvarr[100] = {0};//array of void * all elements are initialized to NULL</tt><tt> //...use the arrays</tt><tt>}</tt></pre><p>This technique works for any combination of structs and arrays:</p><pre><tt>struct A</tt><tt>{</tt><tt> char name[20];</tt><tt> int age;</tt><tt> long ID;</tt><tt>};</tt><tt>void f()</tt><tt>{</tt><tt> A a[100] = {0};</tt><tt>}</tt></pre><p> </p><h3> <a name="Heading34"> Union Initialization</a></h3><p>You can initialize a union. However, unlike struct initialization, the initialization list of a union must contain only a single initializer, which must refer to the first member in the union. For example</p><pre><tt>union Key</tt><tt>{</tt><tt> int num_key;</tt><tt> void *ptr_key;</tt><tt> char name_key[10];</tt><tt>};</tt><tt>void func()</tt><tt>{</tt><tt> Key key = {5}; // first member of Key is of type int</tt><tt> // any additional bytes initialized to binary zeros</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -