⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch12.htm

📁 c++语言操作手册
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<tt>}</tt></pre><p>When the program is linked, the linker is faced with two identical copies of   <tt>Time::Show()</tt>. Normally, function redefinition causes a link-time error.   Un-inlined functions are a special case, however. Older implementations of C++   coped with this situation by treating an un-inlined function as if it had been   declared <tt>static</tt>. Consequently, each copy of the compiled function was   only visible within the translation unit in which it was declared. This solved   the name clashing problem at the cost of multiple local copies of the same function.   In this case, the <tt>inline</tt> declaration did not boost performance; on   the contrary, every call of the un-inlined function was resolved as an ordinary   function call with the regular overhead. Even worse, the multiple copies of   the function code increased compilation and linkage time and bloated the size   of the executable. Ironically, <i>not</i> declaring <tt>Time::Show()</tt> <tt>inline</tt>   might have yielded better performance! Remember that the programmer is generally   not aware of all the actual costs of this -- the compiler strains quietly, the   linker sighs silently, and the resultant executable is more bloated and sluggish   than ever. But it still works, and the users scratch their heads, saying, "This   object-oriented programming is really awful! I'm sure this app would run much   faster if I'd written it in C!". </p><p>Fortunately, the Standard's specification regarding un-inlined functions was   recently changed. A Standard compliant implementation generates only a single   copy of such a function, regardless of the number of translation units that   define it. In other words, an un-inlined function is treated similarly to an   ordinary function. However, it might take some time for all compiler vendors   to adopt the new specifications.</p><h3> <a name="Heading13"> Additional Issues of Concern</a></h3><p>There are two more conundrums that are associated with inline functions. The   first has to do with maintenance. A function can begin its life as a slim inline   function, offering the benefits that were previously described. At a later phase   in the lifetime of the system, the function body can be extended to include   additional functionality, resulting from changes in the implementation of its   class. Suddenly, the inline substitution can become inefficient or even impossible.   It is therefore important to reconsider the removal of the <tt>inline</tt> specifier   from such functions. For member functions that are defined in the class body,   the change is more complicated because the function definition has to be moved   to a separate source file.</p><p>Another problem might arise when inline functions are used in code libraries.   It is impossible to maintain binary compatibility if the definition of an inline   function changes. In this case, the users must recompile their code to reflect   the change. For a non-inline function, the users only need to relink their code,   which is considerably less of a burden than a massive recompilation and relink.</p><h3> <a name="Heading14"> The Do's and Don'ts of inline</a></h3><p>The lesson here is that <tt>inline</tt> is not a magical potion for enhancing   performance. For very short functions -- for example, accessors, mutators, and   function wrappers (see Chapter 13, "C Language Compatibility Issues") -- the   <tt>inline</tt> specifier can be profitable in terms of both execution speed   and program size. If the inlined function is not very short and it is called   extensively, however, the result can be an increase in the size of the executable.   Furthermore, many processors cache the machine instructions of frequently used   parts of the program; excessive inlining can cause a reduced instruction cache   hit and, consequently, poorer overall performance. The real annoyance occurs   when the compiler refuses to inline a function even though it was declared <tt>inline</tt>.   On older implementations, the result was quite painful. On Standard compliant   implementations, the consequences of un-inlining are less detrimental, but they   are still undesirable. Some compilers are clever enough to figure out on their   own which functions are to be inlined. However, most compilers are less inline-savvy   so it is best to examine the effect of an inline declaration empirically. If   the inline declaration does not enhance performance, avoid it. </p><h2> <a name="Heading15"> Optimizing Memory Usage</a></h2><p>Optimization has several aspects: faster execution speed, efficient usage of   system resources, and minimal usage of memory. In general, code optimization   attempts to improve all these aspects. The declaration relocation technique   that was demonstrated earlier eliminates the unnecessary creation and destruction   of objects, thereby reducing the program's size and accelerating its runtime   speed. However, other optimization techniques are heavily biased toward one   direction -- speedier code or a smaller memory footprint. Sometimes, though,   these goals are mutually exclusive; that is, compacting the memory footprint   engenders slower code, whereas a faster code implies a larger memory footprint.   This section presents various techniques for optimizing, or compacting, the   memory requirements of a program.</p><h3> <a name="Heading16"> Bit Fields</a></h3><p>In both C and C++ it is possible to store and access data directly in the tiniest   possible unit: a single bit. Because a bit is not the natural storage unit for   C/C++ implementations, the use of bit fields can increase the size of the executable   due to the additional maneuvers that are exercised by the processor in accessing   a sequence of one or more bits. This is a clear-cut case of sacrificing runtime   speed for the sake of minimizing memory usage.</p><blockquote>  <hr>  <strong>NOTE: </strong> Note, however, that some hardware architectures provide   special processor instructions for accessing bits. Therefore, whether bit fields   affect the program's speed or not is very much platform-dependent.   <hr></blockquote><p>Normally, you don't use bit fields just to save a few more bytes. For some   applications, however, the tradeoff between execution speed and storage compaction   is definitely worth its while. For example, the billing system of an average   international telephone company stores every phone call as a record in a relational   database. These records are processed in batch periodically to calculate the   customer's monthly bill. The database stores millions of new records every day,   and it has to keep the customer's billing information for at least one year.   The complete database contains around one billion records at any given time.   Because the database is also backed up periodically, and because it might also   be a distributed database, every record is stored in more than one physical   location. In fact, there might be 20 billion records stored in different backup   generations and distributed portions of the database at any given time. A minimal   billing record contains the customer's ID, a timestamp, codes that indicate   the type of the call (for example, local or long distance) and the tariff (off   peak, peak time). Literally, every bit counts here -- one redundant bit implies   2.5GB of wasted storage! </p><p>A non-space-optimizing definition of the billing record might look like this:</p><pre><tt>struct BillingRec</tt><tt>{</tt><tt>  long cust_id;</tt><tt>  long timestamp;</tt><tt>  enum CallType</tt><tt>  {</tt><tt>    toll_free,</tt><tt>    local,</tt><tt>    regional,</tt><tt>    long_distance,</tt><tt>    international,</tt><tt>    cellular</tt><tt>  } type;</tt><tt>  enum CallTariff</tt><tt>  {</tt><tt>    off_peak,</tt><tt>    medium_rate,</tt><tt>    peak_time</tt><tt>  } tariff;</tt><tt>};</tt></pre><p>A <tt>BillingRec</tt> occupies no fewer than 16 bytes of memory on my 32-bit   machine. Clearly, space is wasted here. The first two fields occupy four bytes   each, as expected. However, the two <tt>enum</tt> variables occupy an additional   eight bytes, even though they both can be safely represented in less than a   single byte. A tweaked version of <tt>BillingRec</tt> can squeeze the <tt>enum</tt>   values into two bit fields:</p><pre><tt>struct BillingRec</tt><tt>{</tt><tt>  long cust_id;</tt><tt>  long timestamp;</tt><tt>  enum CallType</tt><tt>  {</tt><tt>    toll_free,</tt><tt>    local,</tt><tt>    regional,</tt><tt>    long_distance,</tt><tt>    international,</tt><tt>    cellular</tt><tt>  };</tt><tt>  enum CallTariff</tt><tt>  {</tt><tt>    off_peak,</tt><tt>    medium_rate,</tt><tt>    peak_time</tt><tt>  };</tt><tt>  unsigned call: 3; //three bits </tt><tt>  unsigned tariff: 2; //two bits</tt><tt>};</tt></pre><p>The size of <tt>BillingRec</tt> is now 12 bytes. The four bytes that are saved   are equal to megabytes of data storage per day. Still, the size can be reduced   even more. The two bit fields occupy five bits in total, which is less than   a byte. One might therefore expect <tt>BillingRec</tt> to occupy 9 bytes rather   than 12. The problem is that the compiler inserts three additional padding bytes   after the bit fields to align the size of <tt>BillingRec</tt> on a word boundary   (more on member alignment in Chapter 11, "Memory<i> </i>Management"). The additional   padding bytes ensure faster access time -- at the cost of three wasted bytes.   There are two ways to overcome this problem: You can change the compiler's setting   to allow alignment on a byte boundary, or you can change the size of the other   members so that -- in total -- it reaches exactly eight bytes.</p><blockquote>  <hr>  <strong>NOTE: </strong> Note that both solutions might not be portable, and   on some hardware architectures, the compiler will nonetheless insist on word   boundary alignment. Check your compiler's specifications regarding member alignment   settings.   <hr></blockquote><p>Changing the size of the members is somewhat tricky because the first two members   have to become bit fields as well:</p><pre><tt>struct BillingRec</tt><tt>{</tt><tt>  int cust_id: 24; // 23 bits + 1 sign bit</tt><tt>  int  timestamp: 24;</tt><tt>  enum CallType</tt><tt>  {//</tt>...<tt>  };</tt><tt>  enum CallTariff</tt><tt>  {//</tt>...<tt>  };</tt><tt>  unsigned call: 3;</tt><tt>  unsigned tariff: 2;</tt><tt>};</tt></pre><p>This time, <tt>BillingRec</tt> occupies eight bytes in total, which is half   of its original size. The storage that is saved in this example can amount to   10GB annually. Considering the cheap prices of magnetic storage media these   days, saving a few thousand dollars might not seem to be a compelling argument   -- but there is another reason for favoring smaller data storage: the costs   of digital communication. A distributed database has synchronized copies in   multiple sites. The synchronization process is usually done by means of digital   data transfer from the central database to its synchronized copies, and vice   versa. The transmission of millions of records on leased lines is pretty expensive.   But for a phone company that owns these lines, this is not an issue of special   concern; suppose, however, that the company is an international bank that pays   hundreds of dollars for every hour of data transfer. In this case, halving the   data volume is unquestionably profitable. Another point to remember is the Web;   if the telephone company has a Web site that enables its customers to view their   billing information online, the download time of hundreds of records through   analog dialup lines can be cut in half by this tweak. </p><h3> <a name="Heading17"> Unions</a></h3><p>Unions can also be used to minimize memory waste by locating two or more data   members at the same memory address, where the value of (at most) one of the   data members is active at any time. The size of a union is sufficient to hold   the largest of its data members. A union can have member functions, including   a constructor and destructor, but it cannot have virtual member functions. A   union cannot serve as a base class of, nor can it inherit from, another class.   In addition, a union cannot store objects that have nontrivial special member   functions. C++ also supports <i>anonymous unions</i>. An anonymous union is   an unnamed object of an unnamed type (anonymous unions are also discussed in   Chapter 11). For example</p><pre><tt>union { long n; void * p};  // anonymous</tt><tt>n = 1000L;  // members are directly accessed</tt><tt>p = 0; // n is now also 0 </tt></pre><p>Unlike a named union, an anonymous one cannot have member functions or nonpublic   data members. </p><p>When are unions useful? The following class retrieves a person's data from   a database. The key can be either a unique ID number or a person's last name,   but never both at once:</p><pre><tt>class PersonalDetails </tt><tt>{</tt><tt>private:</tt><tt>  char * name;</tt><tt>  long ID;</tt><tt>  //...</tt><tt>public:</tt><tt>  PersonalDetails(const char *nm);  //key is of type char * used</tt><tt>  PersonalDetails(long id) : ID(id) {} //numeric key used	</tt><tt>};</tt></pre><p>Memory is wasted here because only one of the keys can be used at a time. An   anonymous union can be used in this case to minimize memory usage. For example</p><pre><tt>class PersonalDetails </tt><tt>{</tt><tt>private:</tt><tt>  union  //anonymous</tt><tt>  { </tt><tt>    char * name; </tt><tt>    long ID; </tt>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -