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

📄 chapter11.html

📁 Thinking in c++ 2nd edition,c++编程思想(第2版)
💻 HTML
📖 第 1 页 / 共 5 页
字号:
created as a return value of another function or explicitly by the user of your
function. Temporary objects are always <B>const</B>, so if you don&#8217;t use a
<B>const</B> reference, that argument won&#8217;t be accepted by the compiler.
As a very simple example,</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C11:ConstReferenceArguments.cpp</font>
<font color=#009900>// Passing references as const</font>

<font color=#0000ff>void</font> f(<font color=#0000ff>int</font>&amp;) {}
<font color=#0000ff>void</font> g(<font color=#0000ff>const</font> <font color=#0000ff>int</font>&amp;) {}

<font color=#0000ff>int</font> main() {
<font color=#009900>//!  f(1); // Error</font>
  g(1);
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The call to <B>f(1)</B> causes a
compile-time error because the compiler must first create a reference. It does
so by allocating storage for an <B>int</B>, initializing it to one and producing
the address to bind to the reference. The storage <I>must</I> be a <B>const</B>
because changing it would make no sense &#8211; you can never get your hands on
it again. With all temporary objects you must make the same assumption: that
they&#8217;re inaccessible. It&#8217;s valuable for the compiler to tell you
when you&#8217;re changing such data because the result would be lost
information.</FONT><BR></P></DIV>
<A NAME="Heading329"></A><FONT FACE = "Verdana"><H4 ALIGN="LEFT">
Pointer references<BR><A NAME="Index1879"></A><A NAME="Index1880"></A></H4></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In C, if you want to modify the
<I>contents</I> of the pointer rather than what it points to, your function
declaration looks like: </FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>void</font> f(<font color=#0000ff>int</font>**);</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">and you&#8217;d have to take the address
of the pointer when passing it in:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>int</font> i = 47;
<font color=#0000ff>int</font>* ip = &amp;i;
f(&amp;ip); </PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">With references in C++, the syntax is
cleaner. The function argument becomes a reference to a pointer, and you no
longer have to take the address of that pointer. Thus,</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C11:ReferenceToPointer.cpp</font>
#include &lt;iostream&gt;
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;

<font color=#0000ff>void</font> increment(<font color=#0000ff>int</font>*&amp; i) { i++; }

<font color=#0000ff>int</font> main() {
  <font color=#0000ff>int</font>* i = 0;
  cout &lt;&lt; <font color=#004488>"i = "</font> &lt;&lt; i &lt;&lt; endl;
  increment(i);
  cout &lt;&lt; <font color=#004488>"i = "</font> &lt;&lt; i &lt;&lt; endl;
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">By running this program, you&#8217;ll
prove to yourself that the pointer is incremented, not what it points
to.</FONT><A NAME="_Toc312373961"></A><A NAME="_Toc472654936"></A><BR></P></DIV>
<A NAME="Heading330"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Argument-passing
guidelines<BR><A NAME="Index1881"></A><A NAME="Index1882"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Your normal habit when passing an
argument to a function should be to pass by <B>const</B> reference. Although at
first this may seem like only an efficiency
<A NAME="Index1883"></A><A NAME="Index1884"></A>concern (and you normally
don&#8217;t want to concern yourself with efficiency tuning while you&#8217;re
designing and assembling your program), there&#8217;s more at stake: as
you&#8217;ll see in the remainder of the chapter, a copy-constructor is required
to pass an object by value, and this isn&#8217;t always
available.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The efficiency savings can be substantial
for such a simple habit: to pass an argument by value requires a constructor and
destructor call, but if you&#8217;re not going to modify the argument then
passing by <B>const</B> reference only needs an address pushed on the
stack.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In fact, virtually the only time passing
an address <I>isn&#8217;t</I> preferable is when you&#8217;re going to do such
damage to an object that passing by value is the only safe approach (rather than
modifying the outside object, something the caller doesn&#8217;t usually
expect). This is the subject of the next
section.</FONT><A NAME="_Toc305593229"></A><A NAME="_Toc305628701"></A><A NAME="_Toc312373962"></A><A NAME="_Toc472654937"></A><BR></P></DIV>
<A NAME="Heading331"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
The copy-constructor</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now that you understand the basics of the
reference in C++, you&#8217;re ready to tackle one of the more confusing
concepts in the language: the
copy-constructor<A NAME="Index1885"></A><A NAME="Index1886"></A>, often called
<B>X(X&amp;)</B> (&#8220;X of X ref&#8221;). This constructor is essential to
control passing and returning of user-defined types by value during function
calls. It&#8217;s so important, in fact, that the compiler will automatically
synthesize a copy-constructor if you don&#8217;t provide one yourself, as you
will
see.</FONT><A NAME="_Toc312373963"></A><A NAME="_Toc472654938"></A><BR></P></DIV>
<A NAME="Heading332"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Passing &amp; returning by value</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To understand the need for the
copy-constructor, consider the way C handles passing and returning variables by
value
<A NAME="Index1887"></A><A NAME="Index1888"></A><A NAME="Index1889"></A>during
function calls. If you declare a function and make a function
call,</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>int</font> f(<font color=#0000ff>int</font> x, <font color=#0000ff>char</font> c);
<font color=#0000ff>int</font> g = f(a, b);</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">how does the compiler know how to pass
and return those variables? It just knows! The range of the types it must deal
with is so small &#8211; <B>char</B>, <B>int</B>, <B>float</B>, <B>double</B>,
and their variations &#8211; that this information is built into the compiler.
</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you figure out how to generate
<A NAME="Index1890"></A><A NAME="Index1891"></A><A NAME="Index1892"></A>assembly
code with your compiler and determine the statements generated by the function
call to <B>f(&#160;)</B>, you&#8217;ll get the equivalent of:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>push  b
push  a
call  f()
add  sp,4
mov  g, <font color=#0000ff>register</font> a</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This code has been cleaned up
significantly to make it generic; the expressions for <B>b</B> and <B>a</B> will
be different depending on whether the variables are global (in which case they
will be <B>_b</B> and <B>_a</B>) or local (the compiler will index them off the
stack pointer). This is also true for the expression for <B>g</B>. The
appearance of the call to <B>f(&#160;)</B> will depend on your name-decoration
scheme, and &#8220;register a&#8221; depends on how the CPU registers are named
within your assembler. The logic behind the code, however, will remain the
same.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In C and C++, arguments are first pushed
on the stack from right to left, then the function call is made. The calling
code is responsible for cleaning the arguments off the stack (which accounts for
the <B>add sp,4</B>). But notice that to pass the arguments by value, the
compiler simply pushes copies on the stack &#8211; it knows how big they are and
that pushing those arguments makes accurate copies of them.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The return value of <B>f(&#160;)</B> is
placed in a register. Again, the compiler knows everything there is to know
about the return value type because that type is built into the language, so the
compiler can return it by placing it in a register. With the primitive data
types in C, the simple act of copying the bits of the value is equivalent to
copying the object.</FONT><BR></P></DIV>
<A NAME="Heading333"></A><FONT FACE = "Verdana"><H4 ALIGN="LEFT">
Passing &amp; returning large
objects<BR><A NAME="Index1893"></A><A NAME="Index1894"></A><A NAME="Index1895"></A></H4></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">But now consider user-defined types. If
you create a class and you want to pass an object of that class by value, how is
the compiler supposed to know what to do? This is not a type built into the
compiler; it&#8217;s a type you have created.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To investigate this, you can start with a
simple structure that is clearly too large to return in
registers:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C11:PassingBigStructures.cpp</font>
<font color=#0000ff>struct</font> Big {
  <font color=#0000ff>char</font> buf[100];
  <font color=#0000ff>int</font> i;
  <font color=#0000ff>long</font> d;
} B, B2;

Big bigfun(Big b) {
  b.i = 100; <font color=#009900>// Do something to the argument</font>
  <font color=#0000ff>return</font> b;
}

<font color=#0000ff>int</font> main() {
  B2 = bigfun(B);
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Decoding the assembly output is a little
more complicated here because most compilers use &#8220;helper&#8221; functions
instead of putting all functionality inline. In <B>main(&#160;)</B>, the call to
<B>bigfun(&#160;)</B> starts as you might guess &#8211; the entire contents of
<B>B</B> is pushed on the stack. (Here, you might see some compilers load
registers with the address of the <B>Big</B> and its size, then call a helper
function <A NAME="Index1896"></A><A NAME="Index1897"></A>to push the <B>Big</B>
onto the stack.)</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the previous code fragment, pushing
the arguments onto the stack was all that was required before making the
function call. In <B>PassingBigStructures.cpp</B>, however, you&#8217;ll see an
additional action: the address of <B>B2</B> is pushed before making the call,
even though it&#8217;s obviously not an argument. To comprehend what&#8217;s
going on here, you need to understand the constraints on the compiler when
it&#8217;s making a function call.</FONT><BR></P></DIV>
<A NAME="Heading334"></A><FONT FACE = "Verdana"><H4 ALIGN="LEFT">
Function-call stack
frame<BR><A NAME="Index1898"></A><A NAME="Index1899"></A></H4></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When the compiler generates code for a
function call, it first pushes all the arguments on the stack, then makes the
call. Inside the function, code is generated to move the stack pointer down even
farther to provide storage for the function&#8217;s local variables.
(&#8220;Down&#8221; is relative here; your machine may increment or decrement
the stack pointer during a push.) But during the assembly-language
CALL<A NAME="Index1900"></A><A NAME="Index1901"></A>, the CPU pushes the address
in the program code where the function call <I>came from</I>, so the
assembly-language RETURN <A NAME="Index1902"></A><A NAME="Index1903"></A>can use
that address to return to the calling point. This address is of course sacred,
because without it your program will get completely lost. Here&#8217;s what the
stack frame looks like after the CALL and the allocation of local variable
storage in the function:</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo14.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The code generated for the rest of the
function expects the memory to be laid out exactly this way, so that it can
carefully pick from the function arguments and local variables without touching
the return address. I shall call this block of memory, which is everything used
by a function in the process of the function call, the <I>function
frame</I>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You might think it reasonable to try to
return values on the stack. The compiler could simply push it, and the function
could return an offset to indicate how far down in the stack the return value
begins.</FONT><BR></P></DIV>
<A NAME="Heading335"></A><FONT FACE = "Verdana"><H4 ALIGN="LEFT">
Re-entrancy</H4></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The problem occurs because functions in C
and C++ support interrupts; that is, the languages are
<I>re-entrant<A NAME="Index1904"></A></I>. They also support recursive function
calls. This means that at any point in the execution of a program an interrupt
can occur without breaking the program. Of course, the person who writes the
interrupt service routine (ISR)<A NAME="Index1905"></A> is responsible for

⌨️ 快捷键说明

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