IntermediateProcedures.pdf
(
658 KB
)
Pobierz
Intermediate Procedures
Intermediate Procedures
3.1
Chapter Overview
Chapter Three
This chapter picks up where the chapter “Introduction to Procedures” in Volume Three leaves off. That
chapter presented a high level view of procedures, parameters, and local variables; this chapter takes a look
at some of the low-level implementation details. This chapter begins by discussing the CALL instruction
and how it affects the stack. Then it discusses activation records and how a program passes parameters to a
procedure and how that procedure maintains local (automatic) variables. Next, this chapter presents an
in-depth discussion of pass by value and pass by reference parameters. This chapter concludes by discussing
procedure variables, procedural parameters, iterators, and the FOREACH..ENDFOR loop.
3.2
Procedures and the CALL Instruction
Most procedural programming languages implement procedures using the call/return mechanism. That
is, some code calls a procedure, the procedure does its thing, and then the procedure returns to the caller. The
call and return instructions provide the 80x86’s
procedure invocation mechanism
. The calling code calls a
procedure with the CALL instruction, the procedure returns to the caller with the RET instruction. For
example, the following 80x86 instruction calls the HLA Standard Library
stdout.newln
routine:
call stdout.newln;
stdout.newln
prints a line feed sequence to the video display and returns control to the instruction immedi-
ately following the “call stdout.newln;” instruction.
The HLA language lets you call procedures using a high level language syntax. Specifically, you may
call a procedure by simply specifying the procedure’s name and (in the case of
stdout.newln
) an empty
parameter list. That is, the following is completely equivalent to “call stdout.newln”:
stdout.newln();
The 80x86 CALL instruction does two things. First, it pushes the address of the instruction immedi-
ately following the CALL onto the stack; then it transfers control to the address of the specified procedure.
The value that CALL pushes onto the stack is known as the
return address
. When the procedure wants to
return to the caller and continue execution with the first statement following the CALL instruction, the pro-
cedure simply pops the return address off the stack and jumps (indirectly) to that address. Most procedures
return to their caller by executing a RET (return) instruction. The RET instruction pops a return address off
the stack and transfers control indirectly to the address it pops off the stack.
By default, the HLA compiler automatically places a RET instruction (along with a few other instruc-
tions) at the end of each HLA procedure you write. This is why you haven’t had to explicitly use the RET
instruction up to this point. To disable the default code generation in an HLA procedure, specify the follow-
ing options when declaring your procedures:
procedure ProcName; @noframe; @nodisplay;
begin ProcName;
.
.
.
end ProcName;
The @NOFRAME and @NODISPLAY clauses are examples of procedure
options
. HLA procedures
support several such options, including RETURNS (See “The HLA RETURNS Option in Procedures” on
page 560.), the @NOFRAME, @NODISPLAY, and @NOALIGNSTACKK. You’ll see the purpose of
@NOALIGNSTACK and a couple of other procedure options a little later in this chapter. These procedure
options may appear in any order following the procedure name (and parameters, if any). Note that @NOF-
Beta Draft - Do not distribute
© 2001, By Randall Hyde
Page 805
Chapter Three
Volume Four
RAME and @NODISPLAY (as well as @NOALIGNSTACK) may only appear in an actual procedure dec-
laration. You cannot specify these options in an external procedure prototype.
The @NOFRAME option tells HLA that you don’t want the compiler to automatically generate entry
and exit code for the procedure. This tells HLA not to automatically generate the RET instruction (along
with several other instructions).
The @NODISPLAY option tells HLA that it should not allocate storage in procedure’s local variable
area for a
display
. The display is a mechanism you use to access non-local VAR objects in a procedure.
Therefore, a display is only necessary if you nest procedures in your programs. This chapter will not con-
sider the display or nested procedures; for more details on the display and nested procedures see the appro-
priate chapter in Volume Five. Until then, you can safely specify the @NODISPLAY option on all your
procedures. Note that you may specify the @NODISPLAY option independently of the @NOFRAME
option. Indeed, for all of the procedures appearing in this text up to this point specifying the @NODIS-
PLAY option makes a lot of sense because none of those procedures have actually used the display. Proce-
dures that have the @NODISPLAY option are a tiny bit faster and a tiny bit shorter than those procedures
that do not specify this option.
The following is an example of the minimal procedure:
procedure minimal; nodisplay; noframe; noalignstk;
begin minimal;
ret();
end minimal;
If you call this procedure with the CALL instruction,
minimal
will simply pop the return address off the
stack and return back to the caller. You should note that a RET instruction is absolutely necessary when you
specify the @NOFRAME procedure option
1
. If you fail to put the RET instruction in the procedure, the
program will not return to the caller upon encountering the “end minimal;” statement. Instead, the program
will fall through to whatever code happens to follow the procedure in memory. The following example pro-
gram demonstrates this problem:
program missingRET;
#include( “stdlib.hhf” );
// This first procedure has the NOFRAME
// option but does not have a RET instruction.
procedure firstProc; @noframe; @nodisplay;
begin firstProc;
stdout.put( “Inside firstProc” nl );
end firstProc;
//
//
//
//
//
Because the procedure above does not have a
RET instruction, it will “fall through” to
the following instruction. Note that there
is no call to this procedure anywhere in
this program.
procedure secondProc; @noframe; @nodisplay;
begin secondProc;
1. Strictly speaking, this isn’t true. But some mechanism that pops the return address off the stack and jumps to the return
address is necessary in the procedure’s body.
Page 806
© 2001, By Randall Hyde
Version: 9/12/02
Intermediate Procedures
stdout.put( “Inside secondProc” nl );
ret();
end secondProc;
begin missingRET;
// Call the procedure that doesn’t have
// a RET instruction.
call firstProc;
end missingRET;
Program 3.1
Effect of Missing RET Instruction in a Procedure
Although this behavior might be desirable in certain rare circumstances, it usually represents a defect in
most programs. Therefore, if you specify the @NOFRAME option, always remember to explicitly return
from the procedure using the RET instruction.
3.3
Procedures and the Stack
Since procedures use the stack to hold the return address, you must exercise caution when pushing and
popping data within a procedure. Consider the following simple (and defective) procedure:
procedure MessedUp; noframe; nodisplay;
begin MessedUp;
push( eax );
ret();
end MessedUp;
At the point the program encounters the RET instruction, the 80x86 stack takes the form shown in Fig-
ure 3.1:
Beta Draft - Do not distribute
© 2001, By Randall Hyde
Page 807
Chapter Three
Volume Four
Previous
Stack
Contents
Return Address
Saved EAX
Value
ESP
Figure 3.1
Stack Contents Before RET in “MessedUp” Procedure
The RET instruction isn’t aware that the value on the top of stack is not a valid address. It simply pops
whatever value is on the top of the stack and jumps to that location. In this example, the top of stack con-
tains the saved EAX value. Since it is very unlikely that EAX contains the proper return address (indeed,
there is about a one in four billion chance it is correct), this program will probably crash or exhibit some
other undefined behavior. Therefore, you must take care when pushing data onto the stack within a proce-
dure that you properly pop that data prior to returning from the procedure.
Note:
if you do not specify the @NOFRAME option when writing a procedure, HLA
automatically generates code at the beginning of the procedure that pushes some data onto
the stack. Therefore, unless you understand exactly what is going on and you’ve taken
care of this data HLA pushes on the stack, you should never execute the bare RET instruc-
tion inside a procedure that does not have the @NOFRAME option. Doing so will
attempt to return to the location specified by this data (which is not a return address) rather
than properly returning to the caller. In procedures that do not have the @NOFRAME
option, use the EXIT or EXITIF statements to return from the procedure (See
“BEGIN..EXIT..EXITIF..END” on page 740.).
Popping extra data off the stack prior to executing the RET statement can also create havoc in your pro-
grams. Consider the following defective procedure:
procedure MessedUpToo; noframe; nodisplay;
begin MessedUpToo;
pop( eax );
ret();
end MessedUpToo;
Upon reaching the RET instruction in this procedure, the 80x86 stack looks something like that shown
in Figure 3.2:
Page 808
© 2001, By Randall Hyde
Version: 9/12/02
Intermediate Procedures
Previous
Stack
Contents
ESP
EAX
Return Address
Return Address
Figure 3.2
Stack Contents Before RET in MessedUpToo
Once again, the RET instruction blindly pops whatever data happens to be on the top of the stack and
attempts to return to that address. Unlike the previous example, where it was very unlikely that the top of
stack contained a valid return address (since it contained the value in EAX), there is a small possibility that
the top of stack in this example actually
does
contain a return address. However, this will not be the proper
return address for the
MessedUpToo
procedure; instead, it will be the return address for the procedure that
called
MessUpToo.
To understand the effect of this code, consider the following program:
program extraPop;
#include( “stdlib.hhf” );
// Note that the following procedure pops
// excess data off the stack (in this case,
// it pops messedUpToo’s return address).
procedure messedUpToo; @noframe; @nodisplay;
begin messedUpToo;
stdout.put( “Entered messedUpToo” nl );
pop( eax );
ret();
end messedUpToo;
procedure callsMU2; @noframe; @nodisplay;
begin callsMU2;
stdout.put( “calling messedUpToo” nl );
messedUpToo();
//
//
//
//
//
Because messedUpToo pops extra data
off the stack, the following code
never executes (since the data popped
off the stack is the return address that
points at the following code.
Beta Draft - Do not distribute
© 2001, By Randall Hyde
Page 809
Plik z chomika:
jezuss
Inne pliki z tego folderu:
IntroductionToProcedures.pdf
(400 KB)
LexicalNesting.pdf
(297 KB)
Volume1.pdf
(29 KB)
DataRepresentation.pdf
(470 KB)
ClassesAndObjects.pdf
(472 KB)
Inne foldery tego chomika:
Linux - Administrator
Linux - net
Linux - Podrecznik Administratora Sieci
Linux podrecznik administratora - (PL) [PDF]
Pawel Krawczyk - Ruting IP w Linuxie 2.2
Zgłoś jeśli
naruszono regulamin