In der Computerprogrammierung ist eine Funktion (auch Prozedur , Methode , Unterprogramm , Routine oder Unterprogramm ) eine aufrufbare Einheit [ 1 ] der Softwarelogik , die über eine wohlgeformte Schnittstelle und ein wohlgeformtes Verhalten verfügt und mehrfach aufgerufen werden kann.
Aufrufbare Einheiten stellen ein leistungsstarkes Programmierwerkzeug dar. [ 2 ] Ihr Hauptzweck besteht darin , große und/oder komplexe Probleme in überschaubare Teilprobleme zu zerlegen und diesen Teilproblemen aussagekräftige Namen zuzuweisen (sofern sie nicht anonym sind). Durch eine sinnvolle Anwendung lassen sich die Kosten für die Softwareentwicklung und -wartung senken und gleichzeitig Qualität und Zuverlässigkeit erhöhen. [ 3 ]
Aufrufbare Einheiten existieren auf verschiedenen Abstraktionsebenen der Programmierumgebung. [ 4 ] Beispielsweise kann ein Programmierer eine Funktion im Quellcode schreiben, die zu Maschinencode kompiliert wird und eine ähnliche Semantik implementiert . Es gibt eine aufrufbare Einheit im Quellcode und eine zugehörige im Maschinencode, aber es handelt sich um unterschiedliche Arten von aufrufbaren Einheiten mit unterschiedlichen Implikationen und Eigenschaften.
Terminologie
Einige Programmiersprachen , wie COBOL und BASIC , unterscheiden zwischen Funktionen mit Rückgabewert (üblicherweise „Funktionen“ genannt) und solchen ohne Rückgabewert (üblicherweise „Unterprogramm“, „Subroutine“ oder „Prozedur“ genannt). Andere, wie C , C++ und Rust , verwenden ausschließlich den Begriff „Funktion“, unabhängig davon, ob ein Wert zurückgegeben wird oder nicht. Wieder andere, wie ALGOL 60 und PL/I , verwenden ausschließlich den Begriff „ Prozedur “ . Objektorientierte Sprachen wie Java und C# bezeichnen Funktionen innerhalb von Klassen als „ Methoden “.
Geschichte
Die Idee einer aufrufbaren Einheit wurde ursprünglich von John Mauchly und Kathleen Antonelli während ihrer Arbeit am ENIAC entwickelt und in einem Harvard-Symposium im Januar 1947 zum Thema „Vorbereitung von Problemen für Maschinen des Typs EDVAC “ festgehalten. [ 5 ] Maurice Wilkes , David Wheeler und Stanley Gill gelten allgemein als die formalen Erfinder dieses Konzepts, das sie als geschlossene Unterroutine bezeichneten [ 6 ] [ 7 ], im Gegensatz zu einer offenen Unterroutine oder einem Makro . [ 8 ] Alan Turing hatte jedoch bereits 1945 in einer Arbeit über Entwurfsvorschläge für den NPL ACE Unterroutinen diskutiert und dabei sogar das Konzept eines Rücksprungadressenstapels eingeführt . [ 9 ]
Die Idee der Unterroutine entstand erst, nachdem Rechenmaschinen bereits seit einiger Zeit existierten. Arithmetische Operationen und bedingte Sprünge wurden im Voraus geplant und haben sich relativ wenig verändert, die speziellen Befehle für Prozeduraufrufe hingegen haben sich im Laufe der Jahre stark gewandelt. Die ersten Computer, wie der Manchester Baby , und einige frühe Mikroprozessoren, wie der RCA 1802 , besaßen keine einzige Anweisung für Unterroutinenaufrufe. Unterroutinen konnten zwar implementiert werden, erforderten aber, dass Programmierer an jeder Aufrufstelle die Aufrufsequenz – eine Folge von Befehlen – verwendeten .
Eine einzelne Subroutine wurde 1945 in Konrad Zuses Z4 in Form eines Bandes implementiert. [ 10 ]
1945 verwendete Alan Turing die Begriffe „bury“ und „unbury“ als Mittel zum Aufrufen und Zurückkehren aus Unterprogrammen. [ 11 ] [ 12 ]
Im Januar 1947 präsentierte John Mauchly allgemeine Anmerkungen auf einem Symposium über großtechnische digitale Rechenmaschinen, das gemeinsam von der Harvard University und dem Bureau of Ordnance der US-Marine veranstaltet wurde. Hier erörtert er den seriellen und parallelen Betrieb und schlägt Folgendes vor:
...die Struktur der Maschine muss nicht im Geringsten kompliziert sein. Da alle für dieses Verfahren wesentlichen logischen Merkmale vorhanden sind, ist es möglich, eine Codierungsanweisung zu entwickeln, die die Unterprogramme an der Maschine bekannten Speicheradressen ablegt, sodass sie leicht aufgerufen werden können.
In other words, one can designate subroutine A as division and subroutine B as complex multiplication and subroutine C as the evaluation of a standard error of a sequence of numbers, and so on through the list of subroutines needed for a particular problem. ... All these subroutines will then be stored in the machine, and all one needs to do is make a brief reference to them by number, as they are indicated in the coding.[5]
Kay McNulty had worked closely with John Mauchly on the ENIAC team and developed an idea for subroutines for the ENIAC computer she was programming during World War II.[13] She and the other ENIAC programmers used the subroutines to help calculate missile trajectories.[13]
Goldstine and von Neumann wrote a paper dated 16 August 1948 discussing the use of subroutines.[14]
Some very early computers and microprocessors, such as the IBM 1620, the Intel 4004 and Intel 8008, and PIC microcontrollers, have a single-instruction subroutine call that uses a dedicated hardware stack to store return addresses; such hardware supports only a few levels of subroutine nesting, but can support recursive subroutines. Machines before the mid-1960s, such as the UNIVAC I, the PDP-1, and the IBM 1130, typically use a calling convention which saved the instruction counter in the first memory location of the called subroutine. This allows arbitrarily deep levels of subroutine nesting but does not support recursive subroutines. The IBM System/360 had a subroutine call instruction that placed the saved instruction counter value into a general-purpose register; with additional code, this can be used to support arbitrarily deep subroutine nesting and recursive subroutines. The Burroughs B5000[15] (1961) is one of the first computers to store subroutine return data on a stack.
The DEC PDP-6[16] (1964) is one of the first accumulator-based machines to have a subroutine call instruction that saved the return address in a stack addressed by an accumulator or index register. The later PDP-10 (1966), PDP-11 (1970) and VAX-11 (1976) lines followed suit; this feature also supports both arbitrarily deep subroutine nesting and recursive subroutines.[17]
Language support
In den frühen Assemblern war die Unterstützung für Unterprogramme begrenzt. Unterprogramme waren weder explizit voneinander noch vom Hauptprogramm getrennt, und der Quellcode eines Unterprogramms konnte sogar mit dem anderer Unterprogramme vermischt sein. Einige Assembler boten vordefinierte Makros zur Generierung der Aufruf- und Rücksprungsequenzen an. Ab den 1960er-Jahren verfügten Assembler in der Regel über eine deutlich ausgefeiltere Unterstützung für sowohl inline als auch separat assemblierte Unterprogramme, die miteinander verknüpft werden konnten.
Eine der ersten Programmiersprachen, die benutzerdefinierte Unterprogramme und Funktionen unterstützte, war FORTRAN II . Der IBM FORTRAN II-Compiler wurde 1958 veröffentlicht. ALGOL 58 und andere frühe Programmiersprachen unterstützten ebenfalls die prozedurale Programmierung.
Bibliotheken
Trotz dieses umständlichen Ansatzes erwiesen sich Unterprogramme als sehr nützlich. Sie ermöglichten die Verwendung desselben Codes in vielen verschiedenen Programmen. Speicherplatz war auf frühen Computern eine sehr knappe Ressource, und Unterprogramme ermöglichten eine erhebliche Reduzierung der Programmgröße.
Viele frühe Computer luden die Programmbefehle von einem Lochstreifen in den Speicher . Jede Unterroutine konnte dann auf einem separaten Lochstreifen bereitgestellt werden, der vor oder nach dem Hauptprogramm (oder „Mainline“ [ 18 ] ) geladen oder eingefügt wurde; derselbe Unterroutinen-Lochstreifen konnte dann von vielen verschiedenen Programmen verwendet werden. Ein ähnliches Verfahren wurde bei Computern angewendet, die Programmbefehle von Lochkarten luden . Der Name „Unterroutinenbibliothek“ bezeichnete ursprünglich eine Bibliothek im wörtlichen Sinne, die indizierte Sammlungen von Lochstreifen oder Lochkartensätzen zur gemeinsamen Nutzung aufbewahrte.
Rückkehr durch indirekten Sprung
Um die Notwendigkeit selbstmodifizierenden Codes zu beseitigen , stellten Computerentwickler schließlich einen indirekten Sprungbefehl bereit , dessen Operand nicht die Rücksprungadresse selbst, sondern der Speicherort einer Variablen oder eines Prozessorregisters war , das die Rücksprungadresse enthielt.
Auf diesen Computern würde das aufrufende Programm, anstatt den Rücksprung der Funktion zu verändern, die Rücksprungadresse in einer Variablen speichern, sodass nach Beendigung der Funktion ein indirekter Sprung ausgeführt würde, der die Ausführung zu der durch die vordefinierte Variable angegebenen Stelle lenkt.
Zu Unterroutine springen
Ein weiterer Fortschritt war der Sprungbefehl zu einer Unterroutine , der das Speichern der Rücksprungadresse mit dem aufrufenden Sprung kombinierte und dadurch den Aufwand deutlich minimierte. In den 1950er Jahren existierten mindestens fünf Varianten davon.
- Rücksprung: Die Rücksprungadresse wird im ersten Wort der Subroutine gespeichert. Ein frühes Beispiel hierfür ist der Rücksprung (RJ) des UNIVAC 1103A [ 19 ] .
- Indexsprung: Die Rücksprungadresse wird in einem Indexregister gespeichert. Transfer and Set Index (TSX) auf der IBM 704 [ 20 ] ist ein frühes Beispiel.
- Automatische Speicherung an einem dedizierten Speicherort: Der Speicherort Store P (STP) auf dem RCA 501 [ 21 ] ist ein frühes Beispiel.
- Automatische Speicherung in einem bestimmten Register: Die Sequenz- und Folgehistorienregister (SH und CSH) [ 22 ] des Honeywell 800 sind ein frühes Beispiel.
- Rücksprungadresse ändern: Der Befehl „Rücksprungadresse“ erhöht den Inhalt des Programmzählerregisters um eins und ändert den Adressteil eines Sprungbefehls am letzten Wort der Unterroutine in die Rücksprungadresse des Aufrufers. Unmittelbar nach dem Befehl „Rücksprungadresse“ folgt ein Sprungbefehl zum Anfang der Unterroutine. Ein Beispiel hierfür ist der LGP-30 [ 23 ] .
Die IBM System/360-Architektur ist ein Beispiel für das Speichern der Rücksprungadresse in einem bestimmten Register. Die für Prozeduraufrufe vorgesehenen Verzweigungsbefehle BAL oder BALR speichern die Rücksprungadresse in einem im Befehl angegebenen Prozessorregister, üblicherweise Register 14. Um zurückzukehren, muss die Subroutine lediglich einen indirekten Verzweigungsbefehl (BR) über dieses Register ausführen. Benötigt die Subroutine dieses Register für einen anderen Zweck (z. B. zum Aufruf einer anderen Subroutine), speichert sie dessen Inhalt in einem privaten Speicherbereich oder auf dem Registerstapel .
Die HP-2100 -Architektur ist ein Beispiel dafür, wie die Rücksprungadresse im ersten Wort einer Unterroutine gespeichert wird. Der JSB-Befehl speichert die Rücksprungadresse an der Speicheradresse, die das Ziel der Verzweigung ist. Die Ausführung der Prozedur beginnt an der nächsten Speicheradresse. In der HP-2100-Assemblersprache würde man beispielsweise Folgendes schreiben:
... JSB MYSUB (Ruft die Unterroutine MYSUB auf.) BB ... (Kehre hierher zurück, sobald MYSUB fertig ist.) Um eine Unterroutine namens MYSUB aus dem Hauptprogramm aufzurufen. Die Unterroutine würde wie folgt codiert werden:
MYSUB NOP (Speicher für die Rücksendeadresse von MYSUB.) AA ... (Beginn des Körpers von MYSUB.) ... JMP MYSUB,I (Kehrt zum aufrufenden Programm zurück.) Der Befehl JSB speicherte die Adresse des nächsten Befehls (BB) an der als Operand angegebenen Speicheradresse (MYSUB) und sprang anschließend zur nächsten Speicheradresse (AA = MYSUB + 1). Die Unterroutine konnte dann durch den indirekten Sprung JMP MYSUB, I, der zur Speicheradresse MYSUB sprang, zum Hauptprogramm zurückkehren. Compiler für Fortran und andere Sprachen konnten diese Befehle, sofern verfügbar, problemlos nutzen. Dieser Ansatz unterstützte mehrere Aufrufebenen; da jedoch Rücksprungadresse, Parameter und Rückgabewerte einer Unterroutine feste Speicheradressen hatten, waren keine rekursiven Aufrufe möglich.
Übrigens verwendete Lotus 1-2-3 Anfang der 1980er-Jahre eine ähnliche Methode, um die Abhängigkeiten bei Neuberechnungen in Tabellenkalkulationen zu ermitteln. Dazu wurde in jeder Zelle ein Speicherplatz für die Rücksprungadresse reserviert . Da zirkuläre Verweise bei der natürlichen Neuberechnungsreihenfolge nicht zulässig sind, ermöglicht dies einen Baumdurchlauf, ohne Speicherplatz für einen Stack im Speicher reservieren zu müssen, der auf kleinen Computern wie dem IBM PC sehr begrenzt war .
Aufrufliste
Moderne Implementierungen von Funktionsaufrufen verwenden meist einen Aufrufstapel , einen Spezialfall der Datenstruktur Stack , um Funktionsaufrufe und -rückgaben zu realisieren. Jeder Prozeduraufruf erzeugt einen neuen Eintrag, den sogenannten Stackframe , oben auf dem Stapel. Nach der Rückgabe der Prozedur wird der zugehörige Stackframe gelöscht und kann für weitere Prozeduraufrufe verwendet werden. Jeder Stackframe enthält die privaten Daten des jeweiligen Aufrufs, typischerweise die Parameter und internen Variablen der Prozedur sowie die Rücksprungadresse.
Die Aufrufsequenz kann durch eine Folge gewöhnlicher Befehle implementiert werden (ein Ansatz, der auch heute noch in RISC- ( Reduced Instruction Set Computing ) und VLIW-Architekturen ( Very Long Instruction Word ) verwendet wird), aber viele traditionelle Maschinen, die seit den späten 1960er Jahren entwickelt wurden, enthalten spezielle Befehle für diesen Zweck.
Der Aufrufstapel wird üblicherweise als zusammenhängender Speicherbereich implementiert. Ob die unterste Adresse des Stapels die niedrigste oder höchste Adresse innerhalb dieses Bereichs ist, ist eine willkürliche Designentscheidung, sodass der Stapel im Speicher vorwärts oder rückwärts wachsen kann; viele Architekturen haben sich jedoch für die letztere Variante entschieden. [ 24 ]
Manche Architekturen, insbesondere einige Forth -Implementierungen, verwendeten zwei separate Stacks: einen hauptsächlich für Kontrollinformationen (wie Rücksprungadressen und Schleifenzähler) und einen für Daten. Ersterer entsprach einem Aufrufstapel oder funktionierte ähnlich und war für den Programmierer nur indirekt über andere Sprachkonstrukte zugänglich, während der Datenstapel direkter zugänglich war.
Als stapelbasierte Prozeduraufrufe eingeführt wurden, war ein wichtiges Motiv die Einsparung von wertvollem Speicherplatz. [ 25 ] Mit diesem Verfahren muss der Compiler keinen separaten Speicherplatz für die privaten Daten (Parameter, Rücksprungadresse und lokale Variablen) jeder Prozedur reservieren. Der Stapel enthält zu jedem Zeitpunkt nur die privaten Daten der aktuell aktiven Aufrufe (d. h. derjenigen, die aufgerufen wurden, aber noch nicht zurückgekehrt sind). Da Programme üblicherweise aus Bibliotheken zusammengestellt wurden, war (und ist) es nicht ungewöhnlich, Programme mit Tausenden von Funktionen zu finden, von denen jeweils nur wenige aktiv sind. Für solche Programme konnte der Aufrufstapelmechanismus erhebliche Mengen an Speicherplatz einsparen. Tatsächlich kann der Aufrufstapelmechanismus als die früheste und einfachste Methode zur automatischen Speicherverwaltung angesehen werden .
Ein weiterer Vorteil der Aufrufstapelmethode besteht jedoch darin, dass sie rekursive Funktionsaufrufe ermöglicht , da jeder verschachtelte Aufruf derselben Prozedur eine separate Instanz ihrer privaten Daten erhält.
In einer Multithread- Umgebung existieren im Allgemeinen mehrere Stacks. [ 26 ] Eine Umgebung, die Koroutinen oder verzögerte Auswertung vollständig unterstützt , kann andere Datenstrukturen als Stacks verwenden, um ihre Aktivierungsdatensätze zu speichern.
Verzögertes Stapeln
Ein Nachteil des Aufrufstapelmechanismus sind die erhöhten Kosten eines Prozeduraufrufs und seiner zugehörigen Rückgabe. Diese zusätzlichen Kosten umfassen das Inkrementieren und Dekrementieren des Stapelzeigers (und in manchen Architekturen die Prüfung auf Stapelüberlauf ) sowie den Zugriff auf lokale Variablen und Parameter über rahmenrelative statt absoluter Adressen. Diese Kosten können sich in einer längeren Ausführungszeit, einer höheren Prozessorkomplexität oder beidem niederschlagen.
Dieser Mehraufwand ist besonders deutlich und störend bei Blattprozeduren oder Blattfunktionen , die zurückkehren, ohne selbst Prozeduraufrufe durchzuführen. [ 27 ] [ 28 ] Um diesen Mehraufwand zu reduzieren, versuchen viele moderne Compiler, die Verwendung des Aufrufstapels so lange wie möglich hinauszuzögern. Beispielsweise kann der Aufruf einer Prozedur P die Rücksprungadresse und die Parameter der aufgerufenen Prozedur in bestimmten Prozessorregistern speichern und die Kontrolle durch einen einfachen Sprung an den Prozedurkörper übergeben. Kehrt die Prozedur P zurück, ohne weitere Aufrufe zu tätigen, wird der Aufrufstapel überhaupt nicht verwendet. Muss P jedoch eine andere Prozedur Q aufrufen , verwendet sie den Aufrufstapel, um den Inhalt aller Register (wie z. B. die Rücksprungadresse) zu speichern, die nach der Rückkehr von Q benötigt werden .
Merkmale
Eine aufrufbare Einheit ist im Allgemeinen eine Liste von Anweisungen, die, beginnend mit der ersten Anweisung, sequenziell ausgeführt wird, sofern ihre interne Logik keine anderen Anweisungen gibt. Sie kann während der Programmausführung beliebig oft aufgerufen werden . Die Ausführung wird mit der nächsten Anweisung nach dem Aufruf fortgesetzt, sobald die Kontrolle zurückgegeben wird .
Implementierungen
Die Merkmale von Implementierungen aufrufbarer Einheiten haben sich im Laufe der Zeit weiterentwickelt und variieren je nach Kontext. Dieser Abschnitt beschreibt die Merkmale der verschiedenen gängigen Implementierungen.
Allgemeine Merkmale
Die meisten modernen Programmiersprachen bieten Funktionen zum Definieren und Aufrufen von Funktionen, einschließlich Syntax für den Zugriff auf diese Funktionen, darunter:
- Trennen Sie die Implementierung einer Funktion vom Rest des Programms ab.
- Weisen Sie einer Funktion einen Bezeichner , einen Namen, zu.
- Definieren Sie formale Parameter mit einem Namen und einem Datentyp für jeden Parameter.
- Weisen Sie dem Rückgabewert gegebenenfalls einen Datentyp zu.
- Geben Sie im Funktionskörper einen Rückgabewert an.
- Eine Funktion aufrufen
- Geben Sie die tatsächlichen Parameter an , die den formalen Parametern einer aufgerufenen Funktion entsprechen.
- Die Kontrolle wird zum Zeitpunkt des Anrufs wieder an den Anrufer zurückgegeben.
- Den Rückgabewert im Aufrufer verbrauchen
- Die von einem Aufruf zurückgegebenen Werte können freigegeben werden.
- Stellen Sie einen privaten Namensbereich für Variablen bereit.
- Identifizieren Sie Variablen außerhalb der Funktion, die innerhalb der Funktion zugänglich sind.
- Eine Ausnahmebedingung aus einer Funktion herausgeben und im aufrufenden Kontext behandeln
- Funktionen werden in einem Container wie Modul , Bibliothek , Objekt oder Klasse zusammengefasst.
Benennung
Einige Programmiersprachen, wie Pascal , Fortran , Ada und viele BASIC - Dialekte , verwenden unterschiedliche Bezeichnungen für aufrufbare Einheiten mit Rückgabewert ( Funktion oder Unterprogramm ) und solche ohne Rückgabewert ( Unterprogramm oder Prozedur ). Andere Sprachen, wie C , C++ , C# und Lisp , verwenden nur eine Bezeichnung für aufrufbare Einheiten: Funktion . Die Sprachen der C-Familie verwenden das Schlüsselwort `null`, um anzuzeigen, dass kein Rückgabewert vorhanden ist. void
Aufrufsyntax
Wenn ein Rückgabewert deklariert wird, kann ein Aufruf in einen Ausdruck eingebettet werden , um den Rückgabewert zu verwenden. Beispielsweise könnte eine aufrufbare Einheit zur Berechnung der Quadratwurzel wie folgt aufgerufen werden y = sqrt(x):
Eine aufrufbare Einheit, die keinen Wert zurückgibt, wird als eigenständige Anweisung wie `.` aufgerufen print("hello"). Diese Syntax kann auch für eine aufrufbare Einheit verwendet werden, die einen Wert zurückgibt; der Rückgabewert wird jedoch ignoriert.
Einige ältere Programmiersprachen benötigen ein Schlüsselwort für Aufrufe, die keinen Rückgabewert verbrauchen, wie z. B. CALL print("hello").
Parameter
Die meisten Implementierungen, insbesondere in modernen Sprachen, unterstützen Parameter , die von der aufrufbaren Funktion als formale Parameter deklariert werden . Der Aufrufer übergibt dann die tatsächlichen Parameter , auch Argumente genannt , an die entsprechende Funktion. Verschiedene Programmiersprachen verwenden unterschiedliche Konventionen für die Übergabe von Argumenten.
| Konvention | Beschreibung | Wird verwendet in |
|---|---|---|
| nach Wert | Eine Kopie des Arguments wird übergeben | Standard in den meisten Algol-ähnlichen Sprachen nach Algol 60 , wie z. B. Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada und vielen anderen, einschließlich C, C++ und Java. |
| durch Bezugnahme | Es wird ein Verweis auf das Argument übergeben, typischerweise dessen Adresse. | Auswählbar in den meisten Algol-ähnlichen Sprachen nach Algol 60 , wie z. B. Algol 68, Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada und vielen anderen, einschließlich C++, Fortran, PL/I. |
| Ergebnis | Der während des Aufrufs berechnete Wert wird beim Rückgabewert in das Argument kopiert. | Ada OUT-Parameter |
| nach Wert-Ergebnis | Es wird eine Kopie des Arguments übergeben, und der während des Aufrufs berechnete Wert wird bei der Rückgabe in das Argument kopiert. | Algol, Swift Ein- und Ausgabeparameter |
| nach Namen | Ähnlich wie bei einem Makro – die Parameter werden durch die nicht ausgewerteten Argumentausdrücke ersetzt, und das Argument wird dann jedes Mal im Kontext des Aufrufers ausgewertet, wenn die aufrufbare Funktion den Parameter verwendet. | Algol, Scala |
| durch konstanten Wert | Ähnlich wie bei der Wertübergabe, nur dass der Parameter als Konstante behandelt wird. | PL/I-Parameter, nicht zuweisbare Parameter, Ada-IN-Parameter |
Rückgabewert
In manchen Programmiersprachen, wie z. B. BASIC, unterscheidet sich die Syntax (d. h. das Schlüsselwort) von Funktionen, die einen Wert zurückgeben, von denen, die keinen Wert zurückgeben. In anderen Sprachen ist die Syntax unabhängig davon gleich. In einigen dieser Sprachen, wie z. B. voidC, C++ und C#, wird ein zusätzliches Schlüsselwort verwendet, um keinen Rückgabewert zu deklarieren. In manchen Sprachen, wie z. B. Python, liegt der Unterschied darin, ob der Funktionskörper eine return-Anweisung mit einem Wert enthält. Die Funktion kann je nach Kontrollfluss einen Wert zurückgeben oder nicht.
Nebenwirkungen
In vielen Kontexten kann eine aufrufbare Funktion Nebenwirkungen haben, wie z. B. das Ändern übergebener oder globaler Daten, das Lesen oder Schreiben auf ein Peripheriegerät , den Zugriff auf eine Datei , das Anhalten des Programms oder des Rechners oder das vorübergehende Unterbrechen der Programmausführung.
Nebenwirkungen werden von Robert C. Martin , der für sein Engagement für Designprinzipien bekannt ist, als unerwünscht betrachtet. Martin argumentiert, dass Nebenwirkungen zu zeitlicher Kopplung oder Abhängigkeiten in der Reihenfolge führen können. [ 29 ]
In streng funktionalen Programmiersprachen wie Haskell dürfen Funktionen keine Seiteneffekte haben , das heißt, sie können den Zustand des Programms nicht verändern. Funktionen liefern bei gleicher Eingabe immer dasselbe Ergebnis. Solche Sprachen unterstützen typischerweise nur Funktionen, die einen Wert zurückgeben, da eine Funktion ohne Rückgabewert und ohne Seiteneffekte keinen Wert besitzt.
Lokale Variablen
Die meisten Kontexte unterstützen lokale Variablen – Speicherbereiche , die einer aufrufbaren Funktion zugeordnet sind, um Zwischenwerte zu speichern. Diese Variablen werden typischerweise im Aktivierungsdatensatz des Aufrufs auf dem Aufrufstapel zusammen mit anderen Informationen wie der Rücksprungadresse gespeichert .
Verschachtelter Aufruf – Rekursion
Sofern die Sprache es unterstützt, kann eine aufrufbare Funktion sich selbst aufrufen, wodurch ihre Ausführung unterbrochen wird, während eine weitere, verschachtelte Ausführung derselben Funktion ausgeführt wird. Rekursion ist ein nützliches Mittel, um komplexe Algorithmen zu vereinfachen und komplexe Probleme zu zerlegen. Rekursive Sprachen erzeugen bei jedem Aufruf eine neue Kopie lokaler Variablen. Soll die rekursive Funktion dieselben Variablen anstelle lokaler Variablen verwenden, deklariert der Programmierer diese üblicherweise in einem gemeinsamen Kontext wie `static` oder `global`.
Programmiersprachen wie ALGOL , PL/I und C sowie moderne Sprachen verwenden fast ausnahmslos einen Aufrufstapel, der üblicherweise von den Befehlssätzen unterstützt wird, um für jeden Aufruf einen Aktivierungsdatensatz bereitzustellen. Dadurch kann ein verschachtelter Aufruf seine lokalen Variablen ändern, ohne die Variablen des unterbrochenen Aufrufs zu beeinflussen.
Rekursion ermöglicht die direkte Implementierung von Funktionalitäten, die durch mathematische Induktion und rekursive Divide-and-Conquer-Algorithmen definiert sind . Hier ist ein Beispiel für eine rekursive Funktion in C zur Berechnung von Fibonacci-Zahlen :
int fibonacci ( unsigned int n ) { if ( n <= 1 ) { return n ; } return fibonacci ( n - 1 ) + fibonacci ( n - 2 ); }Frühe Programmiersprachen wie Fortran unterstützten anfangs keine Rekursion, da für jede aufrufbare Funktion nur ein Satz von Variablen und eine Rücksprungadresse reserviert wurden. [ 30 ] Die Speicherung von Rücksprungadressen und Variablen auf dem Stack war bei frühen Computerbefehlssätzen schwierig. Rechner mit Indexregistern oder Allzweckregistern , z. B. die Serien CDC 6000 , PDP-6 , GE 635 , System/360 und UNIVAC 1100 , konnten eines dieser Register als Stackpointer verwenden .
Verschachtelter Gültigkeitsbereich
Einige Sprachen, z. B. Ada , Pascal , PL/I , Python , unterstützen die Deklaration und Definition einer Funktion innerhalb eines Funktionskörpers, sodass der Name der inneren Funktion nur innerhalb des Körpers der äußeren Funktion sichtbar ist.
Ein einfaches Beispiel in Pascal:
function E ( x : real ) : real ; function F ( y : real ) : real ; begin F := x + y end ; begin E := F ( 3 ) + F ( 4 ) end ;Die Funktion Fist in verschachtelt E. Beachten Sie, dass Eder Parameter von xauch in sichtbar ist F(da Fer Teil von ist E), während sowohl xals auch außerhalb von bzw. yunsichtbar sind . EF
Wiedereintritt
Kann eine Funktion auch dann korrekt ausgeführt werden, wenn bereits eine andere Ausführung derselben Funktion läuft, spricht man von Wiederaufrufbarkeit . Wiederaufrufbare Funktionen sind in Multithreading- Umgebungen besonders nützlich, da mehrere Threads dieselbe Funktion aufrufen können, ohne sich gegenseitig zu beeinträchtigen. Im IBM CICS- Transaktionsverarbeitungssystem war Quasi-Wiederaufrufbarkeit eine etwas weniger restriktive, aber ähnliche Anforderung für Anwendungsprogramme, die von mehreren Threads gemeinsam genutzt wurden.
Überlastung
Manche Programmiersprachen unterstützen Überladung – das heißt, mehrere Funktionen mit demselben Namen können im selben Gültigkeitsbereich aufgerufen werden, die jedoch unterschiedliche Eingabetypen verarbeiten. Betrachten wir die Quadratwurzelfunktion, angewendet auf reelle Zahlen, komplexe Zahlen und Matrizen. Der Algorithmus ist für jeden Eingabetyp unterschiedlich, und der Rückgabewert kann einen anderen Datentyp haben. Durch die Verwendung von drei separaten Funktionen mit demselben Namen, z. B. ` sqrt` , kann der resultierende Code einfacher zu schreiben und zu warten sein, da jede Funktion einen relativ leicht verständlichen und einprägsamen Namen hat, anstatt längere und kompliziertere Namen wie `sqrt_real` , `sqrt_complex` und `sqrt_matrix` zu verwenden .
Überladung wird in vielen Sprachen mit starker Typisierung unterstützt . Oft wählt der Compiler die aufzurufende Überladung anhand des Typs der Eingabeargumente aus oder schlägt fehl, falls die Eingabeargumente keine Überladung zulassen. Ältere und schwach typisierte Sprachen unterstützen Überladung im Allgemeinen nicht.
Hier ist ein Beispiel für Überladung in C++ , zwei Funktionen area, die unterschiedliche Typen akzeptieren:
// Gibt die Fläche eines Rechtecks zurück, das durch Höhe und Breite definiert ist. double area ( double height , double width ) { return h * w ; }// Gibt die Fläche eines Kreises mit dem Radius radius zurück . double area ( double radius ) { return radius * radius * std :: numbers :: pi ; }int main () { double rectangleArea = area ( 3 , 4 ); double circleArea = area ( 5 ); }PL/I verfügt über das GENERICAttribut, einen generischen Namen für eine Menge von Eintragsreferenzen zu definieren, die mit unterschiedlichen Argumenttypen aufgerufen werden. Beispiel:
DECLARE gen_name GENERIC( Name WHEN(FIXED BINARY), Flamme WHEN(FLOAT), Pfadname ANDERNFALLS);
Für jeden Eintrag können mehrere Argumentdefinitionen angegeben werden. Ein Aufruf von „gen_name“ führt zu einem Aufruf von „name“, wenn das Argument vom Typ FIXED BINARY ist, zu „flame“, wenn es vom Typ FLOAT ist usw. Wenn das Argument keiner der Optionen entspricht, wird „pathname“ aufgerufen.
Schließung
Eine Closure ist eine aufrufbare Funktion, deren Variablenwerte aus der Umgebung, in der sie erstellt wurde, übernommen werden. Closures waren ein charakteristisches Merkmal der Programmiersprache Lisp, die von John McCarthy eingeführt wurde . Je nach Implementierung können Closures als Mechanismus für Seiteneffekte dienen.
Ausnahmeberichterstattung
Neben dem Verhalten im Normalfall muss eine aufrufbare Funktion unter Umständen den Aufrufer über einen während ihrer Ausführung aufgetretenen Ausnahmefall informieren .
Die meisten modernen Programmiersprachen unterstützen Ausnahmen, was einen Ausnahmebehandlungsablauf ermöglicht, bei dem der Aufrufstapel so lange durchlaufen wird, bis ein Ausnahmebehandler gefunden wird, der die Bedingung behandelt.
Sprachen, die keine Ausnahmen unterstützen, können den Rückgabewert verwenden, um den Erfolg oder Misserfolg eines Aufrufs anzuzeigen. Alternativ kann man einen bekannten Speicherort, beispielsweise eine globale Variable, für die Erfolgsanzeige nutzen. Eine aufrufbare Funktion schreibt den Wert, und der Aufrufer liest ihn nach dem Aufruf aus.
Im IBM System/360 , wo von einer Unterroutine ein Rückgabewert erwartet wurde, war dieser oft ein Vielfaches von 4. So konnte er als direkter Index in eine Verzweigungstabelle verwendet werden, die sich häufig unmittelbar nach dem Aufruf befand. Dadurch wurden zusätzliche Bedingungsprüfungen vermieden und die Effizienz weiter gesteigert. In der Assemblersprache des System/360 schrieb man beispielsweise:
BAL 14, SUBRTN01 springt zu einer Unterroutine und speichert die Rücksprungadresse in R14. B TABLE(15) verwendet den Rückgabewert in Register 15, um die Verzweigungstabelle zu indizieren. * Verzweigung zum entsprechenden Verzweigungsbefehl. TABELLE B OK Rückgabecode = 00 GUT } B Fehlercode = 04 Ungültige Eingabe } Verzweigungstabelle B-FEHLER Rückgabecode = 08 Unerwarteter Zustand } Anrufbeantworter
Ein Aufruf verursacht Laufzeit- Overhead , der unter anderem Folgendes umfassen kann:
- Zuweisung und Freigabe von Speicher im Aufrufstapel
- Speichern und Wiederherstellen von Prozessorregistern
- Kopieren von Eingabevariablen
- Werte nach dem Aufruf in den Kontext des Aufrufers kopieren
- Automatische Prüfung des Rückgabecodes
- Behandlung von Ausnahmen
- Dispatching, beispielsweise für eine virtuelle Methode in einer objektorientierten Sprache
Zur Minimierung der Laufzeitkosten von Aufrufen werden verschiedene Techniken eingesetzt.
Compileroptimierung
Manche Optimierungen zur Minimierung des Funktionsaufruf-Overheads erscheinen zwar einfach, sind aber nicht anwendbar, wenn die aufrufbare Funktion Seiteneffekte hat. Beispielsweise kann (f(x)-1)/(f(x)+1)die Funktion im Ausdruck `x` fnicht nur einmal aufgerufen und ihr Wert zweimal verwendet werden, da die beiden Aufrufe unterschiedliche Ergebnisse liefern können. In den wenigen Sprachen, die die Auswertungsreihenfolge der Operanden des Divisionsoperators definieren, xmuss der Wert von `x` vor dem zweiten Aufruf erneut abgerufen werden, da er durch den ersten Aufruf verändert worden sein könnte. Die Bestimmung, ob eine aufrufbare Funktion Seiteneffekte hat, ist schwierig – aufgrund des Satzes von Rice sogar unentscheidbar . Während diese Optimierung in einer rein funktionalen Programmiersprache unbedenklich ist, geht ein Compiler für eine nicht auf funktionale Programmierung beschränkte Sprache typischerweise vom schlimmsten Fall aus, nämlich dass jede aufrufbare Funktion Seiteneffekte haben kann.
Inlining
Inlining eliminiert Aufrufe bestimmter Funktionen. Der Compiler ersetzt jeden Aufruf durch den kompilierten Code der Funktion. Dadurch wird nicht nur der Overhead der Aufrufe vermieden, sondern der Compiler kann den Code des Aufrufers auch effektiver optimieren, indem er Kontext und Argumente des Aufrufs berücksichtigt. Inlining erhöht jedoch üblicherweise die Größe des kompilierten Codes, außer wenn die Funktion nur einmal aufgerufen wird oder der Funktionskörper sehr kurz ist, beispielsweise nur eine Zeile.
Teilen
Aufrufbare Funktionen können innerhalb eines Programms oder separat in einer Bibliothek definiert werden , die von mehreren Programmen verwendet werden kann.
Interoperabilität
Ein Compiler übersetzt Aufruf- und Rückgabeanweisungen gemäß einer festgelegten Aufrufkonvention in Maschinenbefehle. Bei Code, der vom selben oder einem kompatiblen Compiler kompiliert wird, können Funktionen getrennt von den aufrufenden Programmen kompiliert werden. Die den Aufruf- und Rückgabeanweisungen entsprechenden Befehlssequenzen werden als Prolog und Epilog der Prozedur bezeichnet .
Eingebaute Funktionen
Eine eingebaute Funktion ( auch intrinsische Funktion genannt ) ist eine Funktion, für die der Compiler zur Kompilierzeit Code generiert oder sie auf eine andere Weise als andere Funktionen bereitstellt. [ 31 ] Eine eingebaute Funktion muss nicht wie andere Funktionen definiert werden, da sie in die Programmiersprache integriert ist. [ 32 ]
Zur Kompilierzeit ausgewertete Funktionen
In einigen Programmiersprachen, beispielsweise C++ , Rust und Zig , können Funktionen zur Kompilierzeit ausgewertet werden ; dadurch wird der Laufzeitaufwand reduziert, beispielsweise durch den Verzicht auf zusätzliche Stackframes , den geringeren Speicherverbrauch und das Einbetten der Rückgabewerte in den Binärcode selbst. [ 33 ]
Programmierung
Abwägungen
Vorteile
Zu den Vorteilen der Aufteilung eines Programms in Funktionen gehören:
- Die Zerlegung einer komplexen Programmieraufgabe in einfachere Schritte: Dies ist neben Datenstrukturen eines der beiden Hauptwerkzeuge der strukturierten Programmierung .
- Reduzierung von doppeltem Code innerhalb eines Programms
- Ermöglichung der Wiederverwendung von Code in mehreren Programmen
- Eine große Programmieraufgabe auf verschiedene Programmierer oder verschiedene Projektphasen aufteilen
- Details zur Implementierung vor den Benutzern der Funktion verbergen
- Die Lesbarkeit des Codes wird verbessert, indem ein Codeblock durch einen Funktionsaufruf ersetzt wird, dessen aussagekräftiger Funktionsname den Codeblock beschreibt. Dadurch bleibt der aufrufende Code prägnant und lesbar, selbst wenn die Funktion nicht wiederverwendet werden soll.
- Die Nachverfolgbarkeit wird verbessert (d. h. die meisten Programmiersprachen bieten Möglichkeiten, den Aufrufverlauf zu erhalten, der die Namen der beteiligten Funktionen und möglicherweise weitere Informationen wie Dateinamen und Zeilennummern enthält); ohne die Aufteilung des Codes in Funktionen würde das Debuggen stark beeinträchtigt.
Nachteile
Im Vergleich zur Verwendung von Inline-Code verursacht der Aufruf einer Funktion einen gewissen Rechenaufwand im Aufrufmechanismus.
Eine Funktion benötigt typischerweise standardmäßigen Wartungscode – sowohl beim Eintritt als auch beim Austritt aus der Funktion ( Funktionsprolog und -epilog – in der Regel werden dabei mindestens allgemeine Register und die Rücksprungadresse gespeichert).
Konventionen
Es wurden viele Programmierkonventionen bezüglich aufrufbarer Funktionen entwickelt.
Was die Namensgebung betrifft, so benennen viele Entwickler eine aufrufbare Funktion mit einer Phrase, die mit einem Verb beginnt , wenn sie eine bestimmte Aufgabe erfüllt, mit einem Adjektiv , wenn sie eine Anfrage stellt, und mit einem Substantiv, wenn sie zum Ersetzen von Variablen verwendet wird.
Manche Programmierer vertreten die Ansicht, dass eine aufrufbare Funktion genau eine Aufgabe erfüllen sollte. Erfüllt sie mehrere Aufgaben, sollte sie in mehrere aufrufbare Funktionen aufgeteilt werden. Sie argumentieren, dass aufrufbare Funktionen Schlüsselelemente für die Softwarewartung sind und ihre Rollen im Programm klar definiert bleiben müssen.
Befürworter modularer Programmierung plädieren dafür, dass jede aufrufbare Funktion möglichst wenig Abhängigkeit vom restlichen Code aufweisen sollte . Beispielsweise gilt die Verwendung globaler Variablen im Allgemeinen als unklug, da sie eine Kopplung zwischen allen aufrufbaren Funktionen erzeugt, die diese globalen Variablen verwenden. Ist eine solche Kopplung nicht notwendig, empfehlen sie, aufrufbare Funktionen so umzugestalten , dass sie stattdessen übergebene Parameter akzeptieren .
Beispiele
Early BASIC
Frühe BASIC-Varianten verlangen für jede Zeile eine eindeutige Zeilennummer, die die Ausführungsreihenfolge festlegt. Sie bieten keine Trennung des aufrufbaren Codes, keinen Mechanismus zur Übergabe von Argumenten oder zur Rückgabe von Werten, und alle Variablen sind global. Der Befehl ` sub`GOSUB ( Abkürzung für „sub procedure“ , „subprocedure“ oder „subroutine“ ) springt zur angegebenen Zeilennummer und fährt nach der Rückgabe in der nächsten Zeile fort.
10 REM EIN BASISPROGRAMM 20 GOSUB 100 30 GOTO 20 100 INPUT “ GIB MIR EINE ZAHL ” ; N 110 PRINT “ DIE QUADRATWURZEL VON ” ; N ; 120 PRINT “ IST ” ; SQRT ( N ) 130 RETURNDieser Code fordert den Benutzer wiederholt zur Eingabe einer Zahl auf und gibt die Quadratwurzel des Wertes aus. Die Zeilen 100–130 sind die aufzurufenden Funktionen.
Klein und einfach
In Microsoft Small Basic , das sich an Programmieranfänger in textbasierten Sprachen richtet, wird eine aufrufbare Einheit als Subroutine bezeichnet . Das SubSchlüsselwort `start` kennzeichnet den Beginn einer Subroutine, gefolgt von einem Namensbezeichner. Die darauffolgenden Zeilen bilden den Rumpf, der mit dem Schlüsselwort `start` endet EndSub. [ 34 ]
Sub SayHello TextWindow . WriteLine ( „Hallo!“ ) EndSubDies kann als bezeichnet werden SayHello(). [ 35 ]
Visual Basic
In späteren Versionen von Visual Basic (VB), einschließlich der neuesten Produktlinie und VB6 , wird der Begriff „ Prozedur“ für das Konzept der aufrufbaren Einheit verwendet. Das Schlüsselwort ` Subreturn` gibt entweder keinen Wert zurück oder Functioneinen Wert. Im Kontext einer Klasse ist eine Prozedur eine Methode. [ 36 ]
Jeder Parameter hat einen Datentyp , der angegeben werden kann; wird dieser nicht angegeben, wird standardmäßig Objectfür spätere Versionen auf Basis von .NET und für VB6 eine Variante verwendet . [ 37 ]
VB unterstützt die Parameterübergabe per Wert und per Referenz über die Schlüsselwörter `value` ByValbzw. ByRef`reference`. Sofern nicht ByRefexplizit angegeben ist, wird ein Referenzargument übergeben ByVal. Daher ByValwird `value` selten explizit angegeben.
Bei einfachen Datentypen wie Zahlen sind diese Konventionen relativ eindeutig. Die Übergabe eines Parameters ByRefermöglicht es der Prozedur, die übergebene Variable zu verändern, die Übergabe eines anderen Parameters hingegen ByValnicht. Bei Objekten kann die Semantik Programmierer verwirren, da ein Objekt immer als Referenz behandelt wird. Die Übergabe eines Objekts ByValkopiert die Referenz, nicht den Zustand des Objekts. Die aufgerufene Prozedur kann den Zustand des Objekts über ihre Methoden ändern, jedoch nicht die Objektreferenz des eigentlichen Parameters.
Sub DoSomething () ' Hier kommt Code hin End SubDie Funktion gibt keinen Wert zurück und muss eigenständig aufgerufen werden, wie z. B.DoSomething
Funktion GiveMeFive () als Integer GiveMeFive = 5 End FunctionDies gibt den Wert 5 zurück, und ein Aufruf kann Teil eines Ausdrucks wie diesem sein:y = x + GiveMeFive()
Sub AddTwo ( ByRef intValue as Integer ) intValue = intValue + 2 End SubDies hat einen Nebeneffekt – die per Referenz übergebene Variable wird verändert. Die Funktion könnte beispielsweise für eine Variable vwie `v` aufgerufen werden AddTwo(v). Wenn `v` vor dem Aufruf den Wert 5 hat, ist es danach 7.
C und C++
In C und C++ wird eine aufrufbare Einheit als Funktion bezeichnet . Die Definition einer Funktion beginnt mit dem Namen des Typs des Rückgabewerts oder, voidfalls kein Rückgabewert vorhanden ist, mit `null`. Darauf folgen der Funktionsname, die formalen Argumente in Klammern und der Funktionskörper in geschweiften Klammern.
In C++ wird eine in einer Klasse deklarierte Funktion (als nicht-statische Funktion) als Memberfunktion oder Membermethode bezeichnet . Eine Funktion außerhalb einer Klasse kann als freie Funktion bezeichnet werden , um sie von einer Memberfunktion zu unterscheiden. [ 38 ]
void doSomething () { // etwas Code }Diese Funktion gibt keinen Wert zurück und wird immer eigenständig aufgerufen, wie zum BeispieldoSomething()
int returnFive () { return 5 ; }Diese Funktion gibt den ganzzahligen Wert 5 zurück. Der Aufruf kann eigenständig oder in einem Ausdruck wie folgt erfolgen:y = x + returnFive()
void addTwo ( int * pi ) { * pi += 2 ; }Diese Funktion hat einen Nebeneffekt: Sie erhöht den Wert, der über die Adresse übergeben wird, um den Wert des Eingabewerts um 2. Sie kann für eine Variable aufgerufen werden, vwobei addTwo(&v)das kaufmännische Und-Zeichen (&) dem Compiler signalisiert, die Adresse einer Variablen zu übergeben. Wenn v vor dem Aufruf den Wert 5 hat, ist er danach 7.
void addTwo ( int & i ) { i += 2 ; }Diese Funktion erfordert C++ – sie würde in C nicht kompilieren. Sie verhält sich wie das vorherige Beispiel, übergibt aber den Parameter als Referenz anstatt seiner Adresse. Ein Aufruf wie dieser addTwo(v)enthält kein kaufmännisches Und-Zeichen (&), da der Compiler die Übergabe per Referenz ohne Syntax im Aufruf verarbeitet.
PL/I
In PL/I kann einer aufgerufenen Prozedur ein Deskriptor übergeben werden , der Informationen über das Argument enthält, z. B. Zeichenkettenlängen und Arraygrenzen. Dadurch wird die Prozedur allgemeiner und die Notwendigkeit entfällt, dass der Programmierer diese Informationen übergeben muss. Standardmäßig übergibt PL/I Argumente als Referenz. Eine (triviale) Funktion zum Ändern des Vorzeichens jedes Elements eines zweidimensionalen Arrays könnte wie folgt aussehen:
change_sign : Prozedur ( Array ) ; Deklariere Array (*,*) float ; Array = - Array ; Ende change_sign ;Dies könnte mit verschiedenen Arrays wie folgt aufgerufen werden:
/* Erstes Array: Grenzen von -5 bis +10 und 3 bis 9 */ declare array1 (- 5 : 10 , 3 : 9 ) float ; /* Zweites Array: Grenzen von 1 bis 16 und 1 bis 16 */ declare array2 ( 16 , 16 ) float ; call change_sign ( array1 ) ; call change_sign ( array2 ) ;Python
In Python kennzeichnet das Schlüsselwort defden Beginn einer Funktionsdefinition. Die Anweisungen des Funktionskörpers folgen eingerückt in den darauffolgenden Zeilen und enden in der Zeile, die genauso eingerückt ist wie die erste Zeile oder am Dateiende. [ 39 ]
def format_greeting ( name : str ) -> None : return f "Willkommen, { name } !"def greet_martin () -> None : print ( format_greeting ( "Martin" ))Die erste Funktion gibt einen Begrüßungstext zurück, der den vom Aufrufer übergebenen Namen enthält. Die zweite Funktion ruft die erste auf und gibt beispielsweise greet_martin()„Willkommen Martin“ in der Konsole aus.
Prolog
Bei der prozeduralen Interpretation logischer Programme verhalten sich logische Implikationen wie Zielreduktionsverfahren. Eine Regel (oder Klausel ) der Form:
A :- B
was die logische Lesart hat:
A if B
verhält sich wie ein Verfahren, das Ziele, die mit vereinheitlicht werdenA , auf Teilziele reduziert, die Instanzen von sind B.
Betrachten wir beispielsweise das Prolog-Programm:
Mutter_Kind ( Elizabeth , Charles ). Vater_Kind ( Charles , William ). Vater_Kind ( Charles , Harry ). Elternteil_Kind ( X , Y ) :- Mutter_Kind ( X , Y ). Elternteil_Kind ( X , Y ) :- Vater_Kind ( X , Y ).Beachten Sie, dass die Mutterfunktion durch eine Relation dargestellt wird, wie in einer relationalen Datenbank . Relationen in Prolog fungieren jedoch als aufrufbare Einheiten. X=mother(Y)
Beispielsweise erzeugt der Prozeduraufruf die Ausgabe . Dieselbe Prozedur kann aber auch mit anderen Eingabe-Ausgabe-Mustern aufgerufen werden. Zum Beispiel: ?-parent_child(X,charles)X=elizabeth
?- parent_child ( elizabeth , Y ). Y = charles .?- parent_child ( X , Y ). X = elizabeth , Y = charles .X = Charles , Y = Harry .X = Charles , Y = William .?- parent_child ( william , harry ). no .?- parent_child ( elizabeth , charles ). yes .Siehe auch
- Asynchroner Prozeduraufruf – eine Funktion, die aufgerufen wird, nachdem ihre Parameter von anderen Aktivitäten festgelegt wurden.
- Befehls-Abfrage-Trennung – IT-Architektur, die Aktionen und Lesevorgänge trennt (CQS)
- Zusammengesetzte Operation – Grundlegendes Konstrukt der Programmiersprache
- Koroutinen – Funktionen, deren Ausführung man pausieren kann.
- Evaluierungsstrategie – Evaluierungsregeln für Programmiersprachen
- Ereignis (Informatik) – Berechnung eines Zustands, der mit einem Zeitpunkt verknüpft ist
- Funktion (Mathematik) – Zuordnung eines Ausgabewerts zu jedem Eingabewert
- Funktionale Programmierung – Programmierparadigma, das auf der Anwendung und Komposition von Funktionen basiert.
- Fusionierte Operation – Grundlegendes Konstrukt einer Programmiersprache
- Generator (Computerprogrammierung) – Routine, die eine Folge von Werten erzeugt
- Intrinsische Funktion – Funktion, deren Implementierung vom Compiler speziell behandelt wird
- Anonyme Funktion – Funktionsdefinition, die nicht an einen Bezeichner gebunden ist.
- Logische Programmierung – Programmierparadigma basierend auf formaler Logik
- Modulare Programmierung – Code in Module unterteilen
- Operatorüberladung – ein Merkmal einiger Programmiersprachen
- Parameter (Computerprogrammierung)
- Geschütztes Verfahren
- Transklusion – Automatisches Einbetten eines Datensatzes in einen anderen
Referenzen
- ^ „Terminologieglossar“ . nist.gov . NIST . Abgerufen am 9. Februar 2024.
Aufrufbare Einheit: (Von einem Softwareprogramm oder logischen Entwurf) Funktion, Methode, Operation, Unterprogramm, Prozedur oder analoge Struktureinheit, die innerhalb eines Moduls vorkommt.
- ^ Donald E. Knuth (1997). Die Kunst der Computerprogrammierung, Band I: Fundamentale Algorithmen . Addison-Wesley. ISBN 0-201-89683-4Die
- ^ O.-J. Dahl; E. W. Dijkstra; C. A. Hoare (1972). Strukturierte Programmierung . Academic Press. ISBN 0-12-200550-3Die
- ^ „Was ist Abstraktion in der Programmierung? Für Anfänger erklärt“ . freeCodeCamp.org . 21. Dezember 2022. Abgerufen am 25. November 2025 .
- ^ a b Mauchly, JW (1982). „Vorbereitung von Aufgaben für EDVAC-Maschinen“ . In: Randell, Brian (Hrsg.). Die Ursprünge digitaler Computer . Springer. S. 393–397 . doi : 10.1007/978-3-642-61812-3_31 . ISBN 978-3-642-61814-7Die
- ^ Wheeler, DJ (1952). „Die Verwendung von Unterprogrammen in Programmen“ (PDF) . Proceedings of the 1952 ACM national meeting (Pittsburgh) on - ACM '52 . S. 235. doi : 10.1145/609784.609816 .
- ^ Wilkes, MV; Wheeler, DJ; Gill, S. (1951). Preparation of Programs for an Electronic Digital Computer . Addison-Wesley .
- ^ Dainith, John (2004). "„Open Subroutine.“ A Dictionary of Computing“ . Encyclopedia.com . Abgerufen am 14. Januar 2013 .
- ^ Turing, Alan M. (1945), Bericht von Dr. A. M. Turing über Vorschläge zur Entwicklung einer automatischen Rechenmaschine (ACE): Eingereicht beim Exekutivausschuss des NPL im Februar 1946Nachgedruckt in: Copeland, BJ ( Hrsg.) (2005). Alan Turings automatische Rechenmaschine . Oxford: Oxford University Press. S. 383. ISBN 0-19-856593-3Die
- ^ Speiser, Ambros Paul (2002). „Konrad Zuses Z4: Architektur, Programmierung und Modifikationen an der ETH Zürich“. In: Rojas, Rául ; Hashagen, Ulf ( Hrsg .). Die ersten Computer: Geschichte und Architekturen . MIT. S. 263–276 . ISBN 978-0-262-18197-6Die
- ^ Turing, Alan Mathison (19. März 1946) [1945], Vorschläge zur Entwicklung der mathematischen Abteilung einer automatischen Rechenmaschine (ACE)(Anmerkung: Vorgestellt am 19.03.1946 vor dem Exekutivausschuss des National Physical Laboratory (Großbritannien).)
- ^ Carpenter, Brian Edward ; Doran, Robert William (1. Januar 1977) [Oktober 1975]. „Die andere Turingmaschine“ . The Computer Journal . 20 (3): 269–279 . doi : 10.1093/comjnl/20.3.269 .(11 Seiten)
- ^ a b Isaacson, Walter (18. September 2014). „Walter Isaacson über die Frauen von ENIAC“ . Fortune . Archiviert vom Original am 12. Dezember 2018. Abgerufen am 14. Dezember 2018 .
- ^ Herman H. Goldstine; John von Neumann (1947). „Band 3, 12.0 Kombination von Routinen“ (PDF) . Bericht über die mathematischen und logischen Aspekte eines elektronischen Recheninstruments – Teil II, Band I-3, Planung und Codierung von Problemen für ein elektronisches Recheninstrument (PDF) (Technischer Bericht). Institute for Advanced Study .
- ^ Die Betriebsmerkmale der Prozessoren für die Burroughs B5000 (PDF) . Revision A. Burroughs Corporation . 1963. 5000-21005 . Abgerufen am 8. Februar 2024 .
- ^ „Push-Down-Befehle“ (PDF) . Programmed Data Processor 6 – Handbuch (PDF) . S. 37. Abgerufen am 8. Februar 2024 .
- ^ Guy Lewis Steele Jr. AI Memo 443. „Die Entlarvung des Mythos der ‚teuren Prozeduraufrufe‘; oder: Prozeduraufruf-Implementierungen, die als schädlich gelten“ . Abschnitt „C. Warum Prozeduraufrufe einen schlechten Ruf haben“.
- ^ Frank, Thomas S. (1983). Einführung in den PDP-11 und seine Assemblersprache . Prentice-Hall Softwarereihe. Prentice-Hall. S. 195. ISBN 9780134917047Abgerufen am 6. Juli 2016.
Wir könnten unserem Assembler Kopien des Quellcodes für alle unsere nützlichen Unterprogramme zur Verfügung stellen und ihm dann, wenn wir ihm ein Hauptprogramm für die Assemblerprogrammierung vorlegen, mitteilen, welche Unterprogramme im Hauptprogramm aufgerufen werden sollen. [...
] - ^ Das UNIVAC Scientific General Purpose Computer System (Modell 1103 A) – Vorläufige Informationen (PDF) . Remington Rand Engineering Research Associates Division. 1. Dezember 1955. S. 1–10 . Abgerufen am 8. Oktober 2025 .
- ^ „Control Operations“ (PDF) . 704 elektronische Datenverarbeitungsmaschine – Bedienungsanleitung (PDF) . IBM . 1955. S. 25. 24-6661-2 . Abgerufen am 8. Oktober 2025 .
- ^ „Automatische Speicherung des Inhalts des P-Registers (STP)“ (PDF) . RCA 501 Elektronisches Datenverarbeitungssystem – Programmierhandbuch (PDF) . RCA Electronic Data Processing Division. 1958. S. 18. P501-2 . Abgerufen am 8. Oktober 2025 .
- ^ „Historische Register“ (PDF) . Honeywell 800 Transistorized Data processing System – Programmierhandbuch (PDF) . Honeywell . 1960. S. 55. DSI-31 . Abgerufen am 8. Oktober 2025 .
- ^ Royal Precision Electronic Computer LGP - 30 Programming Manual . Port Chester, New York: Royal Precision. April 1957. Abgerufen am 19. November 2023 .
- ^ Tanenbaum, Andrew S.; Austin, Todd (2013). Strukturierte Rechnerorganisation (6. Aufl.). Boston: Pearson. ISBN 978-0-13-291652-3Die
- ^ Tanenbaum, Andrew S.; Austin, Todd (2013). Strukturierte Rechnerorganisation . Immer lernen (6. Aufl.). Boston, Mass. München: Pearson. ISBN 978-0-13-291652-3Die
- ^ Buttlar, Dick; Farrell, Jacqueline; Nichols, Bradford (1996). PThreads Programming: A POSIX Standard for Better Multiprocessing . O'Reilly Media, Inc. S. 2–5 . ISBN 978-1-4493-6475-5OCLC 1036778036 .
- ^ „ARM Information Center“ . Infocenter.arm.com . Abgerufen am 29. September 2013 .
- ^ „x64-Stack-Nutzung“ . Microsoft Learn . Microsoft. Funktionstypen . Abgerufen am 29. Oktober 2025 .
- ^ Martin, Robert C. (1. August 2008). Clean Code: Ein Handbuch für agile Softwareentwicklung (1. Auflage). Pearson . ISBN 9780132350884Abgerufen am 19. Mai 2024 .
- ^ Verhoeff, Tom (2018). „Eine Meisterklasse über Rekursion“ . In: Böckenhauer, Hans-Joachim; Komm, Dennis; Unger, Walter (Hrsg.). Abenteuer zwischen unteren Grenzen und höheren Höhen: Essays zu Ehren von Juraj Hromkovič anlässlich seines 60. Geburtstags . Springer. S. 616. ISBN 978-3-319-98355-4OCLC 1050567095 .
- ^ „Integrierte Funktionen“ . ibm.com . 9. März 2017. Abgerufen am 25. Dezember 2023 .
- ^ Lernmaterial Python . April 2023. S. 87. Abgerufen am 25. Dezember 2023 .
- ^ Microsoft Learn (22. Februar 2023). „constexpr (C++)“ . learn.microsoft.com . Microsoft Learn.
- ^ „Small Basic“ . Small Basic . Abgerufen am 8. Februar 2024 .
- ^ „Small Basic Getting Started Guide: Chapter 9: Subroutines“ . Microsoft. 17. Januar 2024.
- ^ „Prozeduren in Visual Basic“ . Microsoft Learn . 15. September 2021. Abgerufen am 8. Februar 2024 .
- ^ „Dim-Anweisung (Visual Basic)“ . Microsoft Learn . 15. September 2021. Abgerufen am 8. Februar 2024 .
- ^ "Was versteht man unter einer freien Funktion? "
- ^ "4. Weitere Werkzeuge zur Steuerung des Ablaufs — Python 3.9.7-Dokumentation" .