Loading AI tools
Programmiersprache Aus Wikipedia, der freien Enzyklopädie
Eine Maschinensprache, wie sie bei Maschinencode bzw. nativem Code verwendet wird, ist eine Programmiersprache, bei der die Instruktionen, die vom Prozessor ausgeführt werden sollen, als formale Sprachelemente festgelegt sind. Aufgrund ihrer Nähe zur Hardware wird sie auch verallgemeinernd als die „Programmiersprache eines Computers“ bezeichnet.[1] Umfang und Syntax der Maschinenbefehle sind im Befehlssatz definiert und abhängig vom Prozessortyp. Maschinensprache wird meistens als Binärcode oder vereinfacht mithilfe von Hexadezimalzahlen dargestellt.
Ein Maschinenbefehl ist hierbei eine Anweisung an den Prozessor, eine Operation durchzuführen, beispielsweise eine Addition oder einen Wertevergleich. Jede funktionelle Leistung eines Prozessors ist daher Ergebnis der Ausführung von Maschinencode, eines in Maschinensprache vorliegenden Programms.
Programme in Maschinensprache werden üblicherweise nicht vom Programmierer direkt erzeugt, sondern unter Nutzung einer höheren Programmiersprache oder einer Assemblersprache, wobei erst mithilfe eines Compilers bzw. Assemblers ausführbarer Maschinencode entsteht. Wird von „Programmierung in Maschinensprache“ gesprochen, ist damit manchmal fälschlicherweise die Programmierung in Assemblersprache gemeint. Bei der Ausführung durch Interpreter werden dagegen die Maschinenbefehle beim Programmstart oder während der Laufzeit erzeugt.
Manchmal werden Ausdrücke wie „Maschinencode, Maschinensprache, Binärcode, nativer Code, Programmcode“ synonym verwendet.[2] Sie können jedoch zwei unterschiedliche Bedeutungen haben:
Maschinenprogramme finden in allen Geräten mit einem Prozessor Verwendung, also von Großrechnern über Personal Computer und Smartphones bis hin zu eingebetteten (embedded) Systemen in modernen Waschmaschinen, Radios oder Steuerungen im Kraftfahrzeug für ABS oder Airbag. Bei PCs sind sie üblicherweise in ausführbaren Dateien enthalten.
Ausführbare Dateien findet man bei Windows in Dateien unter der Dateinamenserweiterung „.exe“. Unter vielen anderen Betriebssystemen werden ausführbare Dateien auch ohne Dateiendung und in anderen Formaten geführt. Sie werden teils anders bezeichnet, z. B. unter z/OS als Lademodul. Bei vielen eingebetteten Systemen oder Mikrocontrollern befinden sich bestimmte Maschinenprogramme permanent im ROM, z. B. ein Bootloader.
Maschinenprogramme können von Menschen mithilfe eines Hex-Editors oder eines Maschinencode-Monitors betrachtet, prinzipiell auch erstellt und verändert werden. In der Praxis erfolgt die Herstellung eines Maschinenprogrammes jedoch mithilfe eines Assemblers oder Compilers unter Verwendung von Quelltext der jeweiligen Programmiersprache. Maschinencode kann durch einen Disassembler wieder in Assemblerformat rückübersetzt werden, die Umwandlung in eine höhere Programmiersprache durch einen Decompiler unterliegt jedoch starken Einschränkungen.
Das Programm im Maschinencode besteht aus einer Folge von Bytes, die sowohl Befehle als auch Daten repräsentieren. Da dieser Code für den Menschen schwer lesbar ist, werden in der Assemblersprache die Befehle durch besser verständliche Abkürzungen, sogenannte Mnemonics, dargestellt. Dabei können der Operationscode, Quell- und Zielfelder sowie andere Angaben in den Befehlen mit symbolischen Bezeichnern (wie MOVE, PLZ, LAENGE) notiert werden, ggf. ergänzt um numerische Zahlenwerte, z. B. für eine individuelle Längenangabe, Registernummern usw.
Die meisten der vorgenannten, zur Assemblersprache genannten Aspekte gelten in ähnlicher Weise auch für höhere Programmiersprachen – wobei diese sich gegenüber der Assemblersprache durch weitere (Leistungs-)Merkmale unterscheiden.
Intern ist jeder Befehl der Maschinensprache durch einen oder mehrere Zahlenwerte kodiert. Diese Zahlenwerte bestehen aus dem Opcode, der die Art des Befehls festlegt, eventuell gefolgt von einem oder mehreren Bytes an Daten zu diesem Befehl. Eine sinnvolle Folge von solchen Zahlencodes im Hauptspeicher, bzw. als Datei gespeichert, bildet demnach ein Programm. Es gibt nun verschiedene Arten, solche Programme zu erstellen:
Im folgenden Quelltext in der höheren Programmiersprache C wird die Summe der Zahlen 2 und 3 berechnet und das Ergebnis zurückgegeben:
int main() {
int a = 2;
int b = 3;
int c = a + b;
return c;
}
Ein solches Programm, würde es für einen x86-Prozessor kompiliert, könnte folgenden Maschinencode ergeben:
Maschinencode (hexadezimal) |
zugehöriger Assemblercode | zugehöriger C-Code | Erläuterung |
---|---|---|---|
55 48 89 E5 |
push rbp
|
int main() { |
Sichere Register RBP auf dem Stack und setze RBP auf den Wert von Register RSP, dem Stackpointer (gehört nicht zur eigentlichen Berechnung). Diese Vorbereitung ist notwendig, um die Werte der Variablen a, b und c auf dem Stack speichern zu können. |
C7 45 FC 02 | mov DWORD PTR [rbp-4], 2 |
int a = 2; |
Setze Variable a, die durch Register RBP adressiert wird, auf den Wert 2. |
C7 45 F8 03 | mov DWORD PTR [rbp-8], 3 |
int b = 3; |
Setze Variable b, die durch Register RBP adressiert wird, auf den Wert 3. |
8B 45 F8 8B 55 FC 01 D0 89 45 F4 |
mov eax, DWORD PTR [rbp-8]
|
int c = a + b; |
Setze Register EAX auf den Wert von Variable b. Setze Register EDX auf den Wert von Variable a. |
8B 45 F4 | mov eax, DWORD PTR [rbp-12] |
return c; |
Setze Register EAX auf den Wert von Variable c. Weil Register EAX diesen Wert bereits enthält, könnte diese Anweisung in einem optimierten Programm entfallen. |
5D C3 |
pop rbp
|
} |
Setze RBP wieder auf seinen ursprünglichen Wert. Springe zurück an die Stelle des Aufrufs von main. Register EAX enthält den Rückgabewert. |
Ein Compiler könnte daraus zusammen mit weiteren notwendigen Informationen eine ausführbare Datei erzeugen. Zur Ausführung wird der Maschinencode vom Lader des Betriebssystems in den Arbeitsspeicher geladen. Anschließend ruft die Laufzeitumgebung die Funktion main() auf und die CPU beginnt mit der Abarbeitung der Maschinenbefehle.
Der Maschinencode entsteht beim Assemblieren bzw. beim Kompilieren der Quellcodedateien und wird vom „Linkage Editor“, ggf. unter Hinzufügen weiterer Module, als ausführbares Programm in einer Programmbibliothek bereitgestellt. Zur Ausführung wird dieses Programm in den Hauptspeicher geladen. Der Maschinencode dieser Programme enthält Befehle und Daten gemischt – wie dies bei Computern der Von-Neumann-Architektur möglich ist (im Gegensatz z. B. zur Harvard-Architektur).
Die Daten werden entsprechend dem festgelegten Speicherformat angelegt. Der Wert „12“ kann dabei z. B. folgendes Aussehen haben (Darstellung hexadezimal, in minimaler Länge):
Bei längeren Datenfeldern existieren ggf. führende Nullen zusätzlich oder bei Text nachfolgende Leerstellen. Für jedes vorgesehene Datenfeld ist eine 'Adresse' festgelegt, an der es beginnt und wo es entsprechend seiner Länge und seinem Format gespeichert ist.
Die Befehle bestehen aus dem Befehlscode und – je nach Befehl – Parametern unterschiedlicher Struktur. Die nachfolgenden Beispiele sind hexadezimal dargestellt. Befehlsbeispiele:
C5.1C.92A4.8C2B (Trennpunkte nur zur besseren Lesbarkeit eingefügt):
47.80.B654:
<usw>
Im Assemblercode könnte diese Codierung z. B. wie folgt aussehen:
Von einer Hochsprache generiert könnte der Quellcode dagegen lauten:
Bei „Bedingung erfüllt“ wird nach XXX (= reale Adresse 6C4A64) verzweigt, andernfalls wird im Maschinencode mit <usw>
fortgefahren. Häufig generieren Hochsprachen zusätzliche Befehle, z. B. um Feldlängen oder Datenformate zu egalisieren, Register zu laden oder Adressen in Arrays zu berechnen.
Man erkennt, dass die Befehle unterschiedliche Längen aufweisen. Das Steuerwerk des Rechners erkennt die Länge an den ersten beiden Bits des Befehlscodes und schaltet das Befehlszählregister dementsprechend weiter. An genau dieser Stelle wird das Programm fortgesetzt – falls kein Sprungbefehl auszuführen ist.
Speicheradressen werden im Maschinencode immer durch eine (oder zwei) Registerangabe(n), zusätzlich optional durch eine im Befehl angegebene „Distanz“ dargestellt. Zur Ausführung wird beim Programmstart ein bestimmtes Register vom Betriebssystem mit der Adresse geladen, an die das Programm in den Speicher geladen wurde. Von diesem Wert ausgehend, werden im Programmcode (bei ASS programmiert, bei Hochsprachen generiert) die Basisregister geladen, wodurch die mit relativen Adressen versehenen Befehle die tatsächlichen Speicherstellen ansprechen.
Zur Ausführung von Systemfunktionen (wie Ein-/Ausgabebefehle, Abfrage von Datum/Uhrzeit, Tastatureingabe, Laden von Unterprogrammen u. v. a.) wird im Maschinenprogramm lediglich ein Systemaufruf mit dem Befehl 'SVC' (Supervisor Call) abgesetzt. Im zweiten Byte ist die auszuführende Funktion spezifiziert (Verzeichnis siehe[3]); weitere Parameter für die Funktion werden über eine in ihrer Struktur festgelegte Datenschnittstelle übergeben, auf deren Adresse ein implizit vereinbartes (nicht im Befehl angegebenes) Register zeigt. Beispiel: X'05 08' = LOAD, Parameter = Pgm-Name etc. Die die aufgerufenen Funktionen ausführenden Befehle sind Maschinencode des Betriebssystems. Sie werden dort ausgeführt und führen anschließend zu dem dem SVC folgenden Befehl zurück.[4]
Die im Folgenden genannten Mnemonics (Befehlskürzel) wurden exemplarisch gewählt und hängen von der Assemblersprache ab.
Adressierung und Ergebnisanzeige: Fast alle Befehle adressieren die betroffenen Speicherpositionen (häufig Quelle/Ziel, zu vergleichend/Vergleichswert usw.) über definierte Register. Ebenso gibt der Prozessor seine Ergebnisse und relevante Zusatzinformationen über festgelegte Register und/oder über Flags im Statusregister zurück. Dies ermöglicht es, im weiteren Programmablauf diese Informationen auszuwerten und darauf zu reagieren. Die Länge der Befehle und die Größe von Quell- und Zieloperanden können je nach Architektur unterschiedlich sein.
Beispiel: Ein Additionsbefehl wie ADC (add with carry) signalisiert dem weiteren Programmablauf ein Überschreiten des gültigen Wertebereichs über das Setzen des Carry- und Overflow-Flags hinaus.
Unterschiede: Der Befehlsvorrat einzelner Prozessoren ist unterschiedlich. Nicht alle Befehle sind auf jedem Prozessortyp und in jeder Prozessor-Generation verfügbar.
Beispiel: Ein einfacher Grundbefehl wie SHL/SHR, der einen Registerwert um eine bestimmte Anzahl von Stellen nach links oder rechts verschiebt ist schon im 8086 vorhanden. Die mächtigere Variante SHLD/SHRD, welche zusätzlich die entstehenden Leerstellen aus einem anderen Integerwert auffüllt, ist erst ab dem 80386 implementiert.
Mächtigkeit: Der Befehlsvorrat eines Prozessors stellt dabei Befehle unterschiedlich mächtiger Funktionalität bereit. Neben einfachen, einstufigen Grundoperationen stehen auch Befehle zur Verfügung, die mehrere Operationen in einem Befehl bündeln.
Beispiele: Der Befehl CMP (compare) ermöglicht den Vergleich zweier Werte auf <,>, =. Der Befehl XCHG (exchange) vertauscht die Positionen zweier Operanden. Der Befehl CMPXCHG (compare and exchange) kombiniert diese beiden Befehle und ermöglicht einen bedingungsabhängigen Datenaustausch in einem Befehl. Während der Befehl BT (bit test) nur den Zustand eines einzelnen Bits in einem Integerwert prüft, ermöglichen es die Befehle BTC, BTR, und BTS darüber hinaus, das geprüfte Bit abhängig vom Ergebnis der Prüfung zu setzen (BTS), zu löschen (BTR), oder zu invertieren (BTC).
Generell unterscheidet man zwischen CPUs mit RISC- (Reduced instruction set computer) oder CISC- (Complex instruction set computer) Befehlssatz. Erstere haben einen bedeutend weniger mächtigen Befehlssatz, können jeden einzelnen Befehl aber typischerweise in einem Taktzyklus abarbeiten. Moderne CPUs mit CISC-Befehlssatz (darunter fallen heute fast ausschließlich x86-kompatible CPUs) dekodieren zur schnelleren Abarbeitung die komplexen CISC-Befehle zur Ausführung intern in eine RISC-ähnliche Mikrocontroller-Sprache.
Performance: Jeder Befehl wird in einer in Datenblättern angegebenen Anzahl von Taktzyklen des Prozessors abgearbeitet. Deren Kenntnis ermöglicht es dem Programmierer (bei extrem zeitkritischen Anwendungen) beispielsweise, Befehle mit vielen Taktzyklen durch mehrere, in der Summe aber effizientere Befehle zu ersetzen.
Grundlegende Maschinen-Befehle lassen sich in folgende Kategorien unterteilen:
In vielen modernen Prozessoren sind die Befehle der Maschinensprache, zumindest die komplexeren unter ihnen, intern durch Mikroprogramme realisiert. Das ist insbesondere bei der CISC-Architektur der Fall.
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.