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

📄 chapter 11 constructors and destructors.htm

📁 英文版编译器设计:里面详细介绍啦C编译器的设计
💻 HTM
📖 第 1 页 / 共 5 页
字号:
the token queue to write super statements for the remaining base classes. 
<MENU><IMG src="Chapter 11 Constructors and Destructors.files/RULE60S.gif">
  <P><FONT face=arial size=-1><B>Figure {RULE60S}</B> In this rule, we never 
  parse the super statement directly. Instead, we call two functions called 
  CallBaseClassCtors() and CallBaseClassDtors().</FONT></P></MENU>The last 
modification that we need to make is to <TT>RuleSuperStatement()</TT>. We need 
to do three things, primarily. First of all, we have to make sure that the base 
class being accessed actually exists. The user cannot call a 
constructor/destructor on a base class that was never inherited. Next, if the 
current method is a constructor, we need to make sure that the method being 
invoked is a valid constructor for the specified base class. The same rule 
applies for destructors. Finally, we need to flag the base class as having been 
constructed/destructed.
<P>Again, we use the token queue to implement destructors. We do this in order 
to take advantage of existing functionality within the compiler. This approach 
saves us from having to copy and maintain several sections of code, all of which 
do the same basic thing. All super statements within constructors are expanded 
to their full syntax. For example, <PRE>      super A;
</PRE>is expanded to <PRE>      super A::A();
</PRE>This allows us to bypass all of the syntactic exceptions that we would 
otherwise encounter. In a similar manner, destructors are expanded to their full 
form. <PRE>      super A;
</PRE>is expanded to <PRE>      super A::~A();
</PRE>Notice that the destructor is preceeded by a tilde character, which is not 
valid SAL syntax. In truth, this is not legal. However, the compiler is "fooled" 
in two ways. First of all, the name of the destructor is actually "~A", i.e., 
the tilde is actually a part of the destructor's identifier. Second, the name is 
manufactured by the compiler itself as a string. When we call OutSymbol() and 
pass it a string, the string can contain literally any type of garbage. Since 
the contents of the string are never put through the scanner, the compiler never 
has a problem.
<P>The tilde was left out of SAL's grammar in order to keep people from calling 
the destructor directly. The destructor is called only when an object is cleaned 
up by the memory manager.
<P>We look up constructors by utilizing a method belonging to 
<TT>ClassBlock</TT> called <TT>IsCtorDtor()</TT>. 
<TT>ClassBlock::IsCtorDtor()</TT> takes three parameters, all of which are 
tokens. The first and last tokens must be identifiers, and the middle token must 
be the double-colon, "::".
<P>The first token should be an identifier of the base class, and the third 
token should be an identifier for the name of the constructor/destructor being 
called. The return value of this method is a pointer to the <TT>BaseIdent</TT> 
object for the parent class. In order to use <TT>CallBaseClassCtors()</TT> or 
<TT>CallBaseClassDtors()</TT>, <TT>RuleSuperStatement()</TT> needs a specific 
prototype. <PRE>      proc SuperStatement(follow: Set; sblock: ^ClassBlock; DoDtor: bool);
</PRE>Figure {RULE46S} demonstrates one way of implementing 
<TT>RuleSuperStatement</TT>. The only token that gets explicitly eaten by rule 
super statement is supersy. This method essentially translates super statement 
into a procedure call of the constructor/destructor To to this, it uses 
<TT>LookAhead()</TT> to get the three tokens to use for calling 
<TT>ClassBlock::IsCtorDtor()</TT>. Then instead of parsing the token stream 
directly, the implementation calls <TT>RuleIdentExpression()</TT>, treating the 
next group of tokens as if it were a standard method invocation (which would 
normally have been handled through <TT>RuleIdentExpression()</TT>, anyway).
<P>
<MENU><IMG src="Chapter 11 Constructors and Destructors.files/RULE46S.gif">
  <P><FONT face=arial size=-1><B>Figure {RULE46S}</B> The method described here 
  expands each super statement out to its full syntax. (It essentially will 
  always take the lower path) It then verifies that the base class exists, and 
  that the specified constructor/destructor exists, as well. Finally, it calls 
  RuleIdentExpr(), instead of RuleProcFuncCall(). </FONT></P></MENU>We must be 
able to deal with the offsets to shared classes. To do this we use this code 
(Inserted as seen in diagram): <PRE>			if baseIdent-&gt;getIsShared() then  
				Emit(xLLD,  LOCALSTART);		// Get pointer to self.  It is always the first parameter.
				Emit(xLSA, baseIdent-&gt;getOffset());	// Load the address of the delta-offset to the shared group
				Emit(xCOPT, 4);				// Make a copy of the address
				Emit(xLSD, 0);				// Load the delta-offset
				Emit(xADDD);				// Add it to current self
				if  doDtor then
					Emit(xTR);
					Emit1(xJPZ);
				else 
					Emit(xTS);
					Emit1(xJPNZ);
				end if;
				lPC = ForwardJump();
			end if;
</PRE>We must also point the forward jump to the correct spot in the code. So we 
insert this code after the call to RuleIdentExpression if(baseIdent) <PRE>		if baseIdent &amp;&amp; baseIdent-&gt;getIsShared() then
			JumpFix(lPC);
		end if;
</PRE>
<H3>11.6.3 Code Generation for Constructors and Destructors</H3><!-------------------------------------------------------------------------------->There 
remains essentially three areas that need to be fleshed out in this phase for 
everything to work. 
<UL>
  <LI><B>Initialize local/global class instances.</B> For every variable that is 
  a class instance and not just a pointer to a class, we need to generate code 
  that automatically calls the constructor. 
  <LI><B>Destroy local/global class instances.</B> Every variable that is a 
  class instance requires that its destructor be&nbsp;called.&nbsp; This 
  requires some trickery, especially considering the fact that a function can 
  have multiple points of exit. 
  <LI><B>The super statement for constructors and destructors.</B> If we are 
  doing the body for a constructor or a destructor, we need to make sure base 
  class constructors/destructors are invoked. </LI></UL>
<H4>11.6.3.1 Variable Initialization</H4>In chapter 9 in section 9.2.7 we were 
introduced to a new function called <TT>InitVars()</TT>. This function does a 
lot of work for us. One of its jobs is to make a call to a function called 
<TT>NewonItem()</TT>, which calls the class's meta-constructor, which in turn 
invokes the constructor for the most-derived class. This sequence of events is 
not hard to implement and we have already covered it in previous chapters. If 
followed, we are guaranteed that the constructor for the most-derived class will 
be called.
<P>Let's refer for a moment back to chapter 8, in section 8.1. In this section, 
we discussed the initialization steps required for how to set up a procedure or 
function body. Let's take a closer look at item 3 in figure {RULE60}, which is 
labeled "initialize local arrays and records". Section 8.1.3 discusses this in 
greater detail. A close examination of this section and function 
<TT>InitVars()</TT> mentioned in section 9.2.7 will reveal that 
<TT>InitVars()</TT> accomplishes the same task. In fact, we can substitute 
function <TT>InitVars()</TT> for the code mentioned in section 8.1.3.
<P>In addition to properly initializing local variables, we also need to 
initialize global variables. Again, most of the work has already been done for 
us. We can call <TT>InitVars()</TT> in this case, too. Let's look at section 
8.4. In figure {RULE63} in step 6, we allocate local arrays and records. This 
step is discussed in detail in section 8.4.6. Again, we can substitute the code 
in the compiler in this point with a simple call to <TT>InitVars()</TT>. 
<TT>InitVars()</TT> will pass back a flag that we need to keep. We call it like 
this:
<P><PRE>      CallDtors:= InitVars(...);  // Pass in current FunctionBlock pointer, or null for the main proc
</PRE>The return value from <TT>InitVars()</TT> will save us the trouble of 
having to call another function to destroy local classes if there aren't any.
<P>
<H4>11.6.3.2 Variable Cleanup</H4>In order to accomplish the reverse of 
<TT>InitVars()</TT>, we can write a function called <TT>FiniVars()</TT>. This 
will basically walk backwards through the symbol table for the current scope and 
call the metadestructor for all items that have one. We can make it work at the 
global and local level by passing in a pointer to a <TT>FunctionIdent</TT>, 
which by default can be set to <TT>NULL</TT>. If the pointer is <TT>NULL</TT>, 
we will know that we are doing cleanup at the global level. The call to 
<TT>FiniVars()</TT> should take place immediately before the call to 
<TT>CallBaseClassDtors()</TT>. Local items are always constructed <I>after</I> 
the base classes, and are alway destructed <I>before</I> the base classes.
<P><PRE>      proc FiniVars(block: ^FunctionBlock:= NULL)  // Calls the destructor, where needed.
        var
          OP: int8;
          ident, stop ^Ident;
          
      begin
        ident:= null;
        stop:= null;
      
        if block&lt;&gt;null then
            OP:= xLLD;
            ident:= block-&gt;;first_var();
        else
            OP:= xLGD;
            ident:= table.GetCurrentModule()-&gt;Elements.to_front();
        end if;
        
        while ident &lt;&gt; null do // destroy items
			if ident-&gt;obj = varobj
				and (ident-&gt;typ()&gt; = arraytyp and ident-&gt;typ()&lt; = classtyp) 
				and not ident-&gt;plev() 
				and ident-&gt;getExtra()-&gt;MetaDtorProcNum() &lt;&gt; 0 then  
					Emit (OP,ident-&gt;offset());  //*** Get addr
					<B>DeleteItem(ident);          //*** Destroy item  See SAL-Class.cpp</B>
			end if;
        
			if block &lt;&gt; null then
				ident:= table.prev();       // Next local var
			else
   				ident = table.getCurrentModule()-&gt;Elements.next();
			end if;
		loop;
      end proc;
</PRE>
<P>Calling the <TT>DeleteItem()</TT> funciton in this manner will insure that 
the meta-destructor will get called. Since the meta-destructor and the class 
destructor are one in the same, our task is done.</P>
<P>Our call to <TT>FiniVars()</TT> goes right after the call to 
<TT>RuleStatementSequence()</TT>. In figure {RULE60} in chapter 8 this would be 
right after step 4. In figure {RULE63} in chapter 8, this would be right after 
step 7, and would <I>not</I> be optional, as might be indicated by the diagram. 
Unfortunately, the task of cleaning up local and global class instances is not 
as easy as it was for initializing them. The main problem is that although 
functions and procedures all have a single point of entry, they do not 
necessarily have a single point of exit. We can place the calls to 
<TT>FiniVars()</TT> after the calls to <TT>RuleStatementSequence()</TT> in each 
case, but what happens if the programmer uses the return statement somewhere in 
the body of code. The code generated by our call to <TT>FiniVars()</TT> will be 
completely skipped. There&nbsp;many&nbsp;solutions&nbsp;that remedy 
this,&nbsp;all of which modify rule <TT>ReturnStatement()</TT>.&nbsp; We will 
present one here.
<P>The approach that we will take here will spare us a lot of extra coding when 
it comes time to implement exceptions and exception handling.&nbsp; This method 
involves the use of subroutines.&nbsp; We can recall in section 8.3, when we 
implemented the return statement, all we did is generate a return value and emit 
the <TT>RTN</TT> instruction. What we could do is add a call to a cleanup 
subroutine before emitting the <TT>RTN</TT>.&nbsp; If the reader will recall, 
the call frame for each procedure contains five dwords.&nbsp; One of these is 
the address to a cleanup subroutine.&nbsp; See figure {CALLFRM}
<P>
<MENU><IMG src="Chapter 11 Constructors and Destructors.files/CALLFRM.gif">
  <P><FONT face=arial size=-1><B>Figure {CALLFRM}</B> The layout of a 
  function/procedure call frame.</FONT></P></MENU>A subroutine is a lightweight 
procedure. It can be called like a procedure, and will return to the point 
immediately fallowing the original call. However, it does not have a formal call 
frame, and it relies on the one from the current procedure. The return address 
is stored at the top of the procedure stack, at S. More details can be found in 
the <A href="http://topaz.cs.byu.edu/text/html/Textbook/Chapter11/">reference 
section</A>, in the document entitied <I>"SAL VM Instruction Set"</I>. There are 
three instructions for performing subroutines:
<P>
<MENU><B><TT>JSR</TT></B> Jump to SubRoutine. This instruction works like the 
  unconditional branch, <TT>JMP</TT>, but it goes to a subroutine, instead. The 
  return address for the subroutine is placed at the current address in the S 
  register, and then S is incremented by four bytes.
  <P><B><TT>JSRS</TT></B> Jump to SubRoutine on the eeS. This instruction takes 
  the address of the subroutine from the top of the EES. The return address for 
  the subroutine is placed at the current address in the S register, and then S 
  is incremented by four bytes.
  <P><B><TT>RTS</TT></B> ReTurn from Subroutine. Returns from a subroutine. The 
  return PC address is found by decrementing S by four bytes, and taking the 
  dword at that address.
  <P></P></MENU>In order to have the <TT>return</TT> statement behave correctly, 
we need to modify <TT>RuleReturnStatement()</TT> and 
<TT>RuleProcFuncBlock()</TT>. In <TT>RuleRetur

⌨️ 快捷键说明

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