| • Science | • People | • Locations | • Timeline |
| Contents | ||
The first use of subprograms was in assembly languages that did not have a call instruction. On these computers, subroutines needed to be called by a sequence of lower level instructions, possibly implemented as a macro. These instructions typically modified the program code rather like the infamous Cobol alter statement, modifying the address of a branch at a standard location so that it behaved like an explicit return instruction. Even with this cumbersome approach subroutines proved so useful that soon most architectures provided instructions to help with subroutine calls, clear up to explicit call instructions.
When an assembly language program executes a call, program flow jumps to another location, but the address of the next instruction (I.e. the instruction that follows the call instruction in memory.) is kept somewhere to use when returning [unless an execution stack or similar is being used, as once proposed by Henry Baker. The IBM 360 range used to save this address in a register, relying on macros to save and restore deeper return addresses in memory associated with individual subroutines, then using branches to the address specified in the register to accomplish a return. However stacks have proved a very useful approach, and are typically used in more modern architectures. On these, the return address is pushed as a point of return on the stack. The subroutine exits by using a return instruction which takes the pushed return address and jumps to it, so program flow continues right after the call instruction. Due to useage of a stack, a subroutine itself can call further subroutines (nested calls), and of course it can call the same subroutine from several distinct places. Assembly languages generally do not provide programmers with such conveniences as local variables or subroutine parameters. They get to be implemented by passing values in registers or pushing them onto the stack [or another stack, if there is more than one]. When there is just one stack, the typical stack layout reads like this (function1 calls function2) : ... - function1 local variables - parameters for function2 - function1 return address [of the call instruction in question] - function2 local variables. This is with a forwards growing stack, while on many architectures the stack grows backwards in memory. On the other hand, it is quite practical to have two stacks growing towards each other in a common scratch space, using one mainly for control information like return addresses and loop counters and the other for data. (This is what Forth does.)
A subprogram, as its name suggests, somehow behaves like a computer program. Typically, the caller waits for subprograms to finish and continues execution only after a subprogram " returns". Subroutines are often given parameters to refine their behavior or to perform a certain computation with given [variable] values. Generally, subprograms execute their statements from top to bottom.
In most imperative programming languagesIn computer science, imperative programming as opposed to declarative programming, is a programming paradigm that describes computation in terms of a program state and statements that change the program state. In much the same way as the imperative mood i, subprograms may have so-called side-effectsIn computer science, a side-effect is a property of a programming language function that it modifies some state other than its return value. For example, a function might modify a global or "static" variable, modify one of its arguments, write data to a d; that is, they may cause changes that remain after the subprogram has returned. Usually, compilers cannot predict whether a subprogram has a side-effect or not, but can determine if a subprogram calls no other subprograms, or at least no other subprograms that have side-effects. In imperative programming, compilers usually assume every subprogram has a side-effect to avoid complex analysis of execution paths. Because of its side-effects, a subprogram may return different results each time it is called, even if it is called with the same arguments. A simple example is a subprogram that implements a Pseudorandom number generatorA pseudorandom number generator PRNG is an algorithm which generates a sequence of numbers, the elements of which are approximately independent of each other. The outputs of pseudorandom number generators are not truly random—they only approximate some of; i.e. a subprogram that returns a random number each time it is called.
Such behavior is invalid in a strict mathematical sense. An exception to this common behaviour is found in functional programming languagesFunctional programming is a programming paradigm that treats computation as the evaluation of mathematical functions. In contrast to imperative programming, functional programming emphasizes the evaluation of functional expressions, rather than execution, where subprograms can have no side effects, and will always return the same result if repeatedly called with the same arguments. [Note that subprograms are referred to as functions in these languages.]