出典(authority):フリー百科事典『ウィキペディア(Wikipedia)』「2015/10/06 01:19:07」(JST)
出典は列挙するだけでなく、脚注などを用いてどの記述の情報源であるかを明示してください。記事の信頼性向上にご協力をお願いいたします。(2015年8月) |
プログラミング言語
■カテゴリ / ■テンプレート
アセンブリ言語(アセンブリげんご、assembly language)とは、コンピュータ、マイクロコントローラ、その他のプログラム可能な機器を動作させるための機械語を人間にわかりやすい形で記述する、代表的な低水準言語である。なお、Assemblyとは組立を意味し日本語の意味では組立言語となる。
プロセッサが直接実行できる言語は機械語である。機械語はバイナリ(数値)の羅列なので人間には理解しにくい。そこで、機械語を直接記述するのではなく、ニーモニックと呼ぶ命令語でプログラムを記述することで、人間により分かりやすくしたものがアセンブリ言語である。アセンブリ言語の意味は個々のプロセッサに依存する。高水準言語と比べると移植性は低い。
アセンブリ言語で書いたプログラムを機械語プログラムに変換する事をアセンブル (assemble) すると言う。それを行うプログラムの事をアセンブラ (assembler) と言う。なお、アセンブリ言語の意味で「アセンブラ」または「アセンブラ言語」(Assembler Language)と呼ぶ場合がある[注 1][1]。初期のコンピュータでは「アセンブラ」を「アセンブリプログラム」と呼ぶ例もあった[2]。
アセンブリ言語の命令は機械語に対応したものと、アセンブラに対する命令(疑似命令)やマクロがある。[注 2]機械語と1対1で対応し、プログラマがCPUの動作を把握しながらプログラムを記述する事ができる。
アセンブリ言語では機械語に相当する低水準な操作またはオペコードをニーモニックで表す。オペコードによっては、機械語命令の一部として1つまたは複数のオペランドが必要である。また、多くのアセンブリ言語はオペランドとしてラベルやシンボルを使ってアドレスや定数を表すことができ、それらの値をプログラム内にそのまま書く(ハードコーディング)のを防ぐことができる。マクロアセンブラはマクロ命令機能を備えており、アセンブリ言語のテキストに名前を事前に割り当て、その名前を使うことで他のコードにそのテキストを挿入できる。多くのアセンブラは、プログラム開発を支援したり、アセンブリ過程を制御したり、デバッグを支援したりといった付加機構を備えている。
機械語は、実行したい計算の内容をCPUの内部構造に依存した非常に単純な操作に分割・変換したものであるため、人間には理解しづらい。機械語を並べながらプログラミングするのは、人間のプログラマにとっては負担が大きかった。
そこで、機械語そのものを書く代わりに機械語の「意味」に相当する短い記号や単語を対応させ、それを記述してプログラミングをすることが考えられた。
世界で最初に実用的に稼働したノイマン型電子計算機とされるEDSACのローダ(外部記憶装置からプログラムやデータを読み出して主記憶装置に書き込むプログラム)において、既に原始的なアセンブラの機能が実装されている。EDSACはワード指向アーキテクチャで命令長が1ワードの固定長命令のマシンであり、入力装置は紙テープでありキャラクタ指向である。紙テープ上の A 1 0 0 F という文字列から十進法を数値に変換するなどの処理をおこない、「100番地の値をアキュムレータに加算する」という1ワードの命令を生成する、といった機能が、EDSACのローダには実装されていた。
機械語への変換は人間が手で行うこともあり、ハンド・アセンブルと呼ぶ。単に定められた規則に従って記号や単語から機械語を生成するだけなので、自動的に機械語を出力するプログラムが作られるようになった。このプログラムをアセンブラという。
コンピュータの歴史の初期には、このような、プログラムによって機械語プログラムを生成することを自動プログラミングと呼んだ。
ドナルド・ギリースには、まだ発明されていなかったアセンブラを開発中に、フォン・ノイマンに開発を即座に止めるように言われたという、1950年代初期ならではの逸話がある。当時は人間が手作業でもできるような瑣末な仕事をコンピュータにさせるような時代が来るとは考えられておらず、単に時間の無駄だとノイマンは考えたのである。
アセンブラは、アセンブリ命令ニーモニックをオペコードに変換し、シンボル名をメモリ位置や他の実体に変換することでオブジェクトコードを生成する[3]。シンボル名による参照の利用はアセンブラの重要な機能であり、面倒な計算やプログラム修正に伴うアドレスの更新の手間を省くことができる。多くのアセンブラはマクロ機能を備えており、よく使用する命令列をサブルーチン呼び出しではなくインライン展開することができる。
実行ファイルを生成するのにソースファイルを何回パス(走査)するかでアセンブラを分類できる。
どちらの場合も、アセンブラは最初のパスで各命令のサイズを確定させる必要があり、それによって後に出現するシンボルのアドレスを計算する。命令のサイズは後から定義されるオペランドの型や距離に依存することがあるため、アセンブラは最初のパスでは悲観的な見積もりをし、必要に応じてその後のパスまたは errata にて1つ以上のNOP命令(何もしない命令)を挿入してすき間を埋める必要がある。最適化を行うアセンブラでは、最初の悲観的コードをその後のパスで稠密なコードに書き換えてアドレスの再計算を行うことがある。
もともとワンパスアセンブラは高速であるためよく使われていた。マルチパス動作をするには、磁気テープを巻き戻したりパンチカードのデッキをセットし直して読み込む必要があったためである。現代のコンピュータではマルチパスであってもそのような遅延は生じない。マルチパスアセンブラは errata がないため、リンク処理(アセンブラが直接実行コードを生成する場合はローダの処理)が高速化される[4]。
より洗練された高水準アセンブラ(英語版)は言語として次のような抽象化を提供する。
クロスアセンブラはクロスコンパイラと似ており、あるプラットフォームで動作するが、そのプラットフォームではなく別のプラットフォーム向けのオブジェクトコードを生成するアセンブラを指す。小型の組み込みシステムなど、対象システムでアセンブラを動作させられない場合に使用することが多い。その場合、生成した機械語コードを対象システムに転送する何らかの手段が必要である。単に機械語コードをバイト単位に転送することもできるが、ASCII表現の転送用フォーマットがメーカーから提供されている(モトローラのSREC、インテルのHEXなど)。
メタアセンブラは、アセンブリ言語の文法や意味論を記述したものを入力とし、その言語のためのアセンブラを出力するプログラムである[6]。
アセンブリ言語で書かれたプログラムは、プロセッサの命令に対応したニーモニックの列、ディレクティブや擬似命令と呼ばれるメタな文、コメント、データで構成されている。ニーモニックとは、機械語の命令がプロセッサでおこなう動作を表現する英単語ないしその省略(LoadをLDなど)で表現したものである。通常の文はオペコードのニーモニックで始まり、パラメータ(データ、引数)のリストがそれに続く[7]。それらをアセンブラが機械語命令に翻訳し、メモリにロードして実行できる形式にする。
なかには、パイプライン処理などを最適化するために命令順序を入れ替えたり、ラベルの位置関係によってアドレッシングモードを最適化するアセンブラもあり、必ずしもソーステキストの記述とアセンブルの結果が直接対応するとは限らない。
オペランドにはソースとデスティネーションの二種類があり、データとして読み取られるのがソースで、オペコードで示された命令の実行結果が格納されるのがデスティネーションである。ソースには定数・レジスタ・メモリのいずれか、デスティネーションにはレジスタ・メモリのいずれかを指定する。
なお、オペランドの記述順序は、上の例では「デスティネーション, ソース」だが、「ソース, デスティネーション」の順に書くアセンブリ言語もある。また、オペランドの個数は命令によって異なり、典型的なオペランドは0以上のソースと1つのデスティネーションからなるが、プロセッサにより、暗黙のソースやデスティネーションを持つためにそれらを記述しない命令や、オペランドを持たない命令を持つ場合もある。
例えば、次の命令はx86/IA-32プロセッサのもので、8ビット即値をレジスタに入れる命令である。この命令のバイナリコードは 10110 で、その後に3ビットのレジスタを指定する識別子が続く。AL レジスタの識別子は 000 なので、次に示す機械語は AL レジスタに 01100001 というデータをロードする[8]。
10110000 01100001
このバイナリコードを人間が読みやすいように十六進法で表現すると次のようになる。
B0 61
ここで、B0
は「ALに後続の値をコピーする」ことを意味し、61
は 01100001 を十六進法で表したもの(十進法では 97)である。インテルのアセンブリ言語では、この種の命令に MOV というニーモニックを割り当てており、セミコロン以下に説明的コメントを添えたアセンブリ言語での表現は次のようになる。
MOV AL, 61h ; Load AL with 97 decimal (61 hex)
この場合、定数61Hがソース、レジスタALがデスティネーションに該当し、命令が実行されると、定数61Hが、レジスタALに単純に格納される。これが人間にとってはさらに読みやすく覚えやすい。
アセンブリ言語によっては、MOV のようなニーモニックはロードやデータのコピー/移動を行う命令群を表すのに使われ、オペランドの種類によって実際のオペコードが異なっていてもニーモニックとしては同じものを使う。また、データのコピー/移動の方向などによって別々のニーモニックを使うアセンブリ言語もある(「メモリからレジスタへの移動」を L、「レジスタからメモリへの移動」を ST、「レジスタからレジスタへの移動」を LR、「即値をメモリへ移動」を MVI など)。
インテルのオペコード 10110000 (B0
) は8ビットの値を AL レジスタにコピーするが、10110001 (B1
) は CL レジスタにコピーし、10110010 (B2
) は DL レジスタにコピーする。これらをアセンブリ言語で表現すると次のようになる[8]。
MOV AL, 1h ; Load AL with immediate value 1
MOV CL, 2h ; Load CL with immediate value 2
MOV DL, 3h ; Load DL with immediate value 3
MOVの構文には次の例のようにさらに複雑なものもある[9]。
MOV EAX, [EBX] ; Move the 4 bytes in memory at the address contained in EBX into EAX
MOV [ESI+EAX], CL ; Move the contents of CL into the byte at address ESI+EAX
MOV というニーモニックを使った文は、その内容によってアセンブラが 88-8E、A0-A3、B0-B8、C6、C7 のいずれかのオペコードに変換するので、プログラマはオペコードを知る必要がないし、オペコードを覚える必要もない[8]。
アセンブリ言語から機械語への変換はアセンブラが行うが、逆方向の変換の一部は逆アセンブラが行う。高水準言語とは異なり、単純なアセンブリ構文と機械語命令には1対1の関係がある。しかしアセンブラは擬似命令(マクロの一種)を提供していることがあり、1つの擬似命令をよく使われる機械語命令列に展開する。例えば、大小比較による条件分岐命令を持たないマシンの場合、アセンブラで大小比較命令とゼロなら分岐する条件分岐命令(比較命令の結果を使用)に展開される擬似命令を用意したりする。多くの高機能アセンブラは豊富なマクロ言語機能を備えており、複雑なコード列やデータシーケンスを生成できる。
コンピュータ・アーキテクチャはそれぞれ独自の機械語を持つ。アーキテクチャが異なれば、サポートする命令の数や種類が異なり、レジスタの大きさや個数が異なり、データの格納形式が異なる。汎用コンピュータの多くは機能的にほぼ同等だが、実装は異なるので、アセンブリ言語にはその差異が反映されている。
機械語の命令セットは、プロセッサによって異なる。命令セットが同じでも様々なニーモニックやアセンブリ言語の文法が存在しうる。一般にプロセッサの設計者あるいは製造者によって定義されたニーモニックが標準とされるが、アセンブラによってはこれと異なるニーモニックを用いる場合もある(GNUアセンブラのgasのインテルプロセッサ用など)。
アセンブラの開発者によって用語の使い方に大きな差異があり、文の分類などが異なる。例えば、マシンのニーモニックや拡張ニーモニック以外は全て擬似命令と呼ぶ場合もある。典型的なアセンブリ言語は、プログラムの操作の定義に使われる命令文を以下の3種類に分類する。
アセンブリ言語の命令文は一般に非常に単純で、高水準言語のそれとは異なる。一般にニーモニックは1つの機械語命令(オペコード)のシンボル名であり、個々の機械語命令には少なくとも1つのオペコード・ニーモニックが対応している。命令は一般に「オペコード」と0以上の「オペランド」で構成される。多くの命令は1つまたは2つの値を参照する。オペランドには即値(命令内に置かれる値)、レジスタ(暗黙のうちに使用される場合もある)、記憶装置内のデータの位置を示すアドレスなどがある。「拡張ニーモニック」はオペコードと特定オペランドの組合せを表すのに使われることが多い。例えばSystem/360では、BC 命令にマスク15を組み合わせたものが B、BC 命令にマスク0を組み合わせたものが NOP という拡張ニーモニックで表される。
「拡張ニーモニック」は命令の特殊な用途をサポートするのに使われることが多く、本来の命令の名称からはその用途が連想できないときに使うことが多い。例えば、多くのCPUは明示的にNOP命令を用意していないが、その用途に使える命令は存在する。8086では xchg ax,ax という命令が nop として使えるので、アセンブリ言語で nop を記述すると xchg ax,ax という命令に変換される。逆アセンブラにもこのあたりを認識し、xchg ax,ax を nop に変換するものがある。同様にIBMのSystem/360とSystem/370のアセンブラでは、拡張ニーモニック NOP と NOPR を使用し、それぞれ BC と BCR のマスク0の命令に変換する。SPARCアーキテクチャでは、拡張ニーモニックを synthetic instructions と呼んでいる[10]。
アセンブラによっては単純な組み込みマクロ命令を備えており、数個の機械語命令に展開される。例えば一部のZ80用アセンブラでは、ld hl,bc というマクロ命令を ld l,c と ld h,b という2命令に展開する[11]。
データと変数を保持するデータ要素を定義するのに使われる命令文がある。データの型、長さ、境界(アライメント)を定義する。また、そのデータがプログラム外部(別ファイルでアセンブルされたプログラム)からも利用可能なのか、それともデータセクションを定義したプログラム内でのみ使用可能なのかも定義できる。一部のアセンブラはこれを擬似命令に分類している。
アセンブリディレクティブは擬似命令とも呼ばれ、アセンブラがアセンブリ実施中に実行すべき命令となっている[3]。プログラマが入力するパラメータによって異なった形でアセンブルが行われるよう指示することができる。また、プログラムの見た目を操作して、可読性と保守性を向上させるのにも使われる。例えば、記憶装置の領域を予約し、その初期内容を指定するディレクティブなどがある。ディレクティブの名称はドットで始まることが多く、それによって通常のニーモニックと区別している。
擬似オペコード (pseudo-opcode) と言った場合、オブジェクトコードを実際に生成するディレクティブのみを指すこともある[12]。
シンボリックアセンブラでは、任意の名前(ラベルまたはシンボル)とメモリ位置を対応付けることができる。通常、定数や変数に名前をつけることができ、命令文ではそれらの位置を名前で参照できる。実行コードではサブルーチンのエントリポイントと名前を関連付け、サブルーチンを名前で呼び出すことができる。サブルーチン内では、分岐命令の分岐先をラベルで示すことができる。一部のアセンブラは「ローカルシンボル」をサポートしており、通常のシンボルとは語彙的に区別する(例えば、"10$" を分岐先に使用するなど)。
一部のアセンブラは柔軟なシンボル管理を提供しており、複数の名前空間を管理したり、データ構造内のオフセットを自動的に計算したり、リテラル値やアセンブラが実施した単純な計算結果を参照するラベルを割り当てたりすることができる。ラベルは定数や変数をリロケータブルなアドレスで初期化するのにも使える。
他の言語と同様アセンブリ言語でもソースコードにコメントを付与でき、アセンブラはそれらを無視できる。高水準言語に比べて可読性が低いため、コメントの重要性は高い。
これらの機能を多用することで低水準のコードのコーディングや保守の問題を大幅に単純化することができる。コンパイラや逆アセンブラが生成するアセンブリソースはコメントがなく、自動生成されるシンボルが意味不明で、データ定義もないため、非常に可読性が低い。
多くのアセンブラは事前定義されたマクロをサポートしており、中にはユーザーが定義可能なマクロをサポートしているものもある。マクロは命令列と変数や定数の定義を埋め込むことができ、その中にオペコードやディレクティブの列が含まれる。マクロが定義されると、その名前をニーモニックとして使用できる。アセンブラはソース内にマクロを発見すると、対応するコード列に置き換え、元からソースにそのコード列が存在したかのように処理をする(アセンブラによっては、マクロを展開した中にマクロがあれば、それも展開することができる)。
なお、このような「マクロ」の定義は、例えばC言語でのマクロとは若干異なる。C言語のマクロは一般に #define というディレクティブで定義され、1行または多くても数行で済ませることが多い。アセンブラのマクロはそれ自体が長い「プログラム」であり、アセンブラがアセンブル時に解釈して実行する。
マクロは短い名前だが、数行からかなり長い行数のコードに展開されるので、アセンブリ言語のプログラムの見た目を短くすることができる。また、マクロによってアセンブリ言語プログラムに高水準の構造を追加でき、パラメータ付きのデバッグ用コードを埋め込むなどの利用法がある。
マクロアセンブラでは、マクロにパラメータを付与できる。中には洗練されたマクロ言語を備えたアセンブラもあり、オプションパラメータ、記号変数、条件文、文字列操作、算術演算などの高水準言語要素を持ち、マクロ実行中にそれらを使用でき、またマクロがコンテキストをセーブしたり情報を交換したりすることも可能である。したがってマクロの引数に基づいて大量のアセンブリ言語命令またはデータ定義を生成できる。例えば、レコード風のデータ構造や展開されたループを生成するのに使え、複雑なパラメータに基づいてアルゴリズム全体を生成することもできる。そのようなマクロで大いに拡張されたアセンブリ言語を使用すると、コンピュータの低水準の概念要素を扱う必要がなくなり、一種の高水準言語をつかっているのと同じことになる。
メインフレームの時代には、マクロは特定顧客の大規模ソフトウェアシステムのカスタマイズや、メーカーのオペレーティングシステムを顧客の要望に合わせた特注版にするのに使われていた。例えば、IBMの VM/CMS、リアルタイムトランザクション処理用アドオン、CICS、ACP(英語版)/TPF(コンピュータ予約システム (CRS) やクレジットカード会社で使われているトランザクションOS)などで使われてきた。
アセンブラのマクロ機能だけを全く異なる言語のコード生成に使うこともできる。例えば、COBOLのプログラムをマクロアセンブラのアセンブル時に実行されるオペレータで生成することができる。
というのも、1960年代に実用化された「マクロ処理」は「アセンブリ」とは独立した概念であり、マクロ処理は単なるオブジェクトコード生成というよりも現代的にはワードプロセッシングやテキストプロセッシングに近い。C言語にもマクロ処理の概念が導入されており、プリプロセッサと呼ばれる。ただし、アセンブラのマクロプロセッサはチューリング完全だったが、C言語のプリプロセッサはそうではない(Cのプリプロセッサにはループ機能や "goto" 機能がない)。
マクロ処理の能力は大きいが、アセンブラ以外の高水準言語ではあまり使われなくなった(例外としてC/C++とPL/Iがある)。
マクロのパラメータ置換は厳密に名前によってなされる。マクロ処理の際、パラメータ値がその名前を逐語的に置換する。そのため、パラメータとして式を指定したときよくあるバグが生じる。例えば次のようなマクロがあるとする。
foo: macro a load a*b
このマクロは呼び出し側が変数名を指定し、広域変数または定数である b とその変数をかけることを想定している。しかし、foo のパラメータとして a-c を指定するとマクロは load a-c*b と展開されてしまう。これを防ぐにはマクロ定義内でパラメータを常に括弧で囲むか、呼び出し時にパラメータを括弧付きにするしかない[13]。
構造化プログラミングの要素を取り入れたアセンブラもある。最初期の例として "Concept-14 macro set" がある。Dr. H.D. Mills (March, 1970) が提案し、IBMの連邦政府システム部門の Marvin Kessler が実装したもので、System/360 のマクロアセンブラに IF/ELSE/ENDIF などの制御構造を導入した[14]。これはアセンブリコードからGOTO操作を削減または排除するもので、本質的にスパゲッティプログラムになりやすいアセンブリ言語のコードを読みやすくするものだった。1980年代にはこの手法が広まった。
独特の設計として、Whitesmiths Ltd.(Unix系OS Idris やCコンパイラで有名)が開発した8080/Z80プロセッサ用「ストリーム指向」アセンブラ A-natural がある。この言語はオペコードやレジスタ、メモリ参照といった要素をそのまま扱えるためアセンブラと呼ばれているが、実行順序を示す構文を取り入れていた。括弧やその他の記号でプログラムのブロック構造を指定したり、生成した命令列の実行順序を制御できる。A-natural は同社のCコンパイラが中間コードとしており、人間が直接使うものではなかったが、その論理的構文にはファンも存在した。
また構造化プログラミングとは若干異なるが、キャリーラボはBASIC風の文法のアセンブリ言語 BASE を開発した。Z80用の BASE-80 とMC6809用の BASE-09 がある。BASEの表記例は下記の通り(BASE-09)
S[A,B,X,U
A=$80
A=A+$C0
S]A,B,X,U,PC
上記の記述は下記のアセンブラ表記に対応する。
PSHS A,B,X,U LDA #$80 ADDA #$C0 PULS A,B,X,U,PC
その後、アセンブリ言語で大規模システムを開発することが少なくなり、アセンブリ言語の高機能化の需要が減っていった[15]。それでも、資源の制約が強い場合やターゲットシステムのアーキテクチャが高水準言語の効率的使用を妨げている場合、アセンブラは有用であり、高機能なアセンブラの開発も行われている[16]。
アセンブリ言語はプログラム内蔵方式のコンピュータの黎明期に登場した。EDSAC (1949) には initial orders という1文字のニーモニックを採用したアセンブラが存在した[17]。ナサニエル・ロチェスターは1954年に IBM 701 用アセンブラを書いている。1955年、Stan Poley が IBM 650 用アセンブり言語 SOAP (Symbolic Optimal Assembly Program) を開発した[18]。
アセンブリ言語は、初期のコンピュータでのプログラミングでの入力ミス削減や時間短縮に貢献し、機械語コード参照やアドレス計算といった退屈な作業からプログラマを解放した。その後高水準言語へと移行していったが、ハードウェアの直接操作、特殊命令の使用、性能向上といった目的で今もアセンブリ言語が使われている。特にデバイスドライバ、組み込みシステム、リアルタイムシステムでよく使われている。
歴史的には多数のプログラムがアセンブリ言語だけで書かれてきた。ALGOLの方言であるESPOLで書かれた Burroughs MCP (1961) が登場するまで、オペレーティングシステムはアセンブリ言語で書くのが普通だった。商用アプリケーションもアセンブリ言語で書かれており、例えばIBMのメインフレーム用ソフトウェアの多くはアセンブリ言語で書かれていた。COBOL、FORTRAN、PL/I などが取って代わっていったが、1990年代になってもアセンブリ言語のコードベースを保守し続けていた大企業も少なくない。
初期のマイクロコンピュータではアセンブリ言語がよく使われ、OSやアプリケーションも当初はアセンブリ言語で書かれた。これはリソースの制約が厳しかったためであり、メモリやディスプレイのアーキテクチャが特殊だったためでもある。また、マイクロコンピュータ向けの高水準言語のコンパイラがなかったという面も重要である。また、初期のマイクロコンピュータのユーザーはホビーストが主であり、何でも自前で作るという精神もそれに影響していたと見られる。
1980年代から1990年代にかけてのホームコンピュータ(ZX Spectrum、コモドール64、Amiga、Atari ST など)でもアセンブリ言語がよく使われていた。というのもそれらのBASICは性能が低く、ハードウェアの全機能を利用できないことが多かったためである。例えばAmigaにはフリーウェアのアセンブリ言語統合開発環境 ASM-One assembler があり、Microsoft Visual Studio に匹敵する機能を備えていた。
Don French が開発した VIC-20 用アセンブラは 1,639 バイトという小ささで、世界一小さいアセンブラと言われている。アドレスをシンボルで表現でき、各種アドレス計算(四則演算、AND、OR、冪乗など)が可能だった[19]。
商用製品でアセンブリ言語を使う最大の理由は、使用メモリ量を最小にし、オーバーヘッドを最小にし、性能と信頼性を向上させるためである。1980年代のビジネスソフトでは、例えば、IBM PC のDOSや表計算ソフト Lotus 1-2-3 などはアセンブリ言語で書かれていた。日本では松が有名である。
1990年代に入ってもコンシューマーゲームの多くはアセンブリ言語で書かれていた。ある業界関係者はセガサターンの最高性能を引き出してプレイステーションに対抗するにはアセンブリ言語を使うしかなかったとしている(プレイステーションにはソニーが提供するCライブラリが存在した)[20]。また、アーケードゲームのNBAジャム (1993) もアセンブリ言語で開発されている。ただし一方で、ファミコン時代すでにメタルスレイダーグローリーやスーパーファミコンのMOTHER 2・シムシティ[21]、プレステのクラッシュ・バンディクーで[22]、開発の一部にLISPが使われていたという話もあり、アセンブリ言語が全てという世界だった、と認識するのは誤りである。
アセンブリ言語と高水準言語を比較した有用性と性能についての議論は、これまでよく行われてきた。アセンブリ言語は後述するように特定の用途で重要な役割を演じている。しかし一般的には最適化コンパイラが人手で書かれたアセンブリ言語のコードと同等の性能を発揮すると言われている[23](例外もある[24][25][26])。最近のプロセッサやメモリサブシステムは複雑化してきたため、コンパイラでもアセンブリ言語でも効果的な最適化がますます困難になってきている[27][28]。さらに言えば、プロセッサが高性能化するにしたがって入出力やページングによる遅延が無視できないレベルとなったため、コーディングによる性能追求は多くのプログラマにとって大きな問題ではなくなってきている。
開発者がアセンブリ言語を選択する状況として、次のようなものがある。
計算機科学や情報工学では、アセンブリ言語が今も教えられている。実際にアセンブリ言語を使用するプログラマは全体から見れば少ないが、その概念は非常に重要である。二進法、メモリ管理、スタック処理、文字コード、割り込み処理、コンパイラ設計といった基本的トピックは、ハードウェアレベルでのコンピュータの動作を学ばなければ深く理解するのが困難である。コンピュータの動作は基本的には命令セットで定義されるので、アセンブリ言語を学ぶことでそのような概念を習得できる。最近のコンピュータの命令セットはどれも似ている。したがって、どれか1つのアセンブリ言語を学ぶだけで、基本概念、どんなときにアセンブリ言語を使用するのが適しているか、高水準言語から効率的な実行コードを生成する方法を学習できる[32]。これは、子どもが算数(筆算など)を学ぶ必要があるが、日常の計算には電卓を使っているのに似ている。
Unix系システムでは、アセンブラを as と呼ぶのが一般的だが、実体はそれぞれのOSで異なる。GNUアセンブラを使っているものが多い。
同じ系統のプロセッサであっても、複数のアセンブリ言語の方言が存在する。アセンブラによっては他の方言のアセンブリ言語も使用可能な場合がある。例えば、TASMはMASM用コードを入力として受け付け可能だが、逆は不可能である。FASM(英語版)とNASMは文法がほぼ同じだが、サポートしているマクロが異なるため、相互の翻訳は困難である。いずれも基本機能は同じだが、追加機能に差異がある[33]。
Motorola MC6800 Assembly listing, showing original assembly language and the assembled form
|
|
Paradigm | Imperative |
---|---|
First appeared | 1949; 66 years ago (1949) |
An assembly language (or assembler language[1]) is a low-level programming language for a computer, or other programmable device, in which there is a very strong (generally one-to-one) correspondence between the language and the architecture's machine code instructions. Each assembly language is specific to a particular computer architecture, in contrast to most high-level programming languages, which are generally portable across multiple architectures, but require interpreting or compiling.
Assembly language is converted into executable machine code by a utility program referred to as an assembler; the conversion process is referred to as assembly, or assembling the code.
Assembly language uses a mnemonic to represent each low-level machine instruction or operation. Typical operations require one or more operands in order to form a complete instruction, and most assemblers can therefore take labels, symbols and expressions as operands to represent addresses and other constants, freeing the programmer from tedious manual calculations. Macro assemblers include a macroinstruction facility so that (parameterized) assembly language text can be represented by a name, and that name can be used to insert the expanded text into other code. Many assemblers offer additional mechanisms to facilitate program development, to control the assembly process, and to aid debugging.
An assembler is a program that creates object code by translating combinations of mnemonics and syntax for operations and addressing modes into their numerical equivalents. This representation typically includes an operation code ("opcode") as well as other control bits.[2] The assembler also calculates constant expressions and resolves symbolic names for memory locations and other entities.[3] The use of symbolic references is a key feature of assemblers, saving tedious calculations and manual address updates after program modifications. Most assemblers also include macro facilities for performing textual substitution – e.g., to generate common short sequences of instructions as inline, instead of called subroutines.
Some assemblers may also be able to perform some simple types of instruction set-specific optimizations. One concrete example of this may be the ubiquitous x86 assemblers from various vendors. Most of them are able to perform jump-instruction replacements (long jumps replaced by short or relative jumps) in any number of passes, on request. Others may even do simple rearrangement or insertion of instructions, such as some assemblers for RISC architectures that can help optimize a sensible instruction scheduling to exploit the CPU pipeline as efficiently as possible.[citation needed]
Like early programming languages such as Fortran, Algol, Cobol and Lisp, assemblers have been available since the 1950s and the first generations of text based computer interfaces. However, assemblers came first as they are far simpler to write than compilers for high-level languages. This is because each mnemonic along with the addressing modes and operands of an instruction translates rather directly into the numeric representations of that particular instruction, without much context or analysis. There have also been several classes of translators and semi automatic code generators with properties similar to both assembly and high level languages, with speedcode as perhaps one of the better known examples.
There may be several assemblers with different syntax for a particular CPU or instruction set architecture. For instance, an instruction to add memory data to a register in a x86-family processor might be add eax,[ebx]
, in original Intel syntax, whereas this would be written addl (%ebx),%eax
in the AT&T syntax used by the GNU Assembler. Despite different appearances, different syntactic forms generally generate the same numeric machine code, see further below. A single assembler may also have different modes in order to support variations in syntactic forms as well as their exact semantic interpretations (such as FASM-syntax, TASM-syntax, ideal mode etc., in the special case of x86 assembly programming).
There are two types of assemblers based on how many passes through the source are needed to produce the executable program.
In both cases, the assembler must be able to determine the size of each instruction on the initial passes in order to calculate the addresses of subsequent symbols. This means that if the size of an operation referring to an operand defined later depends on the type or distance of the operand, the assembler will make a pessimistic estimate when first encountering the operation, and if necessary pad it with one or more "no-operation" instructions in a later pass or the errata. In an assembler with peephole optimization, addresses may be recalculated between passes to allow replacing pessimistic code with code tailored to the exact distance from the target.
The original reason for the use of one-pass assemblers was speed of assembly – often a second pass would require rewinding and rereading a tape or rereading a deck of cards. With modern computers this has ceased to be an issue. The advantage of the multi-pass assembler is that the absence of errata makes the linking process (or the program load if the assembler directly produces executable code) faster.[4]
More sophisticated high-level assemblers provide language abstractions such as:
See Language design below for more details.
A program written in assembly language consists of a series of (mnemonic) processor instructions and meta-statements (known variously as directives, pseudo-instructions and pseudo-ops), comments and data. Assembly language instructions usually consist of an opcode mnemonic followed by a list of data, arguments or parameters.[6] These are translated by an assembler into machine language instructions that can be loaded into memory and executed.
For example, the instruction below tells an x86/IA-32 processor to move an immediate 8-bit value into a register. The binary code for this instruction is 10110 followed by a 3-bit identifier for which register to use. The identifier for the AL register is 000, so the following machine code loads the AL register with the data 01100001.[7]
10110000 01100001
This binary computer code can be made more human-readable by expressing it in hexadecimal as follows.
B0 61
Here, B0
means 'Move a copy of the following value into AL', and 61
is a hexadecimal representation of the value 01100001, which is 97 in decimal. Assembly language for the 8086 family provides the mnemonic MOV (an abbreviation of move) for instructions such as this, so the machine code above can be written as follows in assembly language, complete with an explanatory comment if required, after the semicolon. This is much easier to read and to remember.
MOV AL, 61h ; Load AL with 97 decimal (61 hex)
In some assembly languages the same mnemonic such as MOV may be used for a family of related instructions for loading, copying and moving data, whether these are immediate values, values in registers, or memory locations pointed to by values in registers. Other assemblers may use separate opcode mnemonics such as L for "move memory to register", ST for "move register to memory", LR for "move register to register", MVI for "move immediate operand to memory", etc.
The x86 opcode 10110000 (B0
) copies an 8-bit value into the AL register, while 10110001 (B1
) moves it into CL and 10110010 (B2
) does so into DL. Assembly language examples for these follow.[7]
MOV AL, 1h ; Load AL with immediate value 1
MOV CL, 2h ; Load CL with immediate value 2
MOV DL, 3h ; Load DL with immediate value 3
The syntax of MOV can also be more complex as the following examples show.[8]
MOV EAX, [EBX] ; Move the 4 bytes in memory at the address contained in EBX into EAX
MOV [ESI+EAX], CL ; Move the contents of CL into the byte at address ESI+EAX
In each case, the MOV mnemonic is translated directly into an opcode in the ranges 88-8E, A0-A3, B0-B8, C6 or C7 by an assembler, and the programmer does not have to know or remember which.[7]
Transforming assembly language into machine code is the job of an assembler, and the reverse can at least partially be achieved by a disassembler. Unlike high-level languages, there is usually a one-to-one correspondence between simple assembly statements and machine language instructions. However, in some cases, an assembler may provide pseudoinstructions (essentially macros) which expand into several machine language instructions to provide commonly needed functionality. For example, for a machine that lacks a "branch if greater or equal" instruction, an assembler may provide a pseudoinstruction that expands to the machine's "set if less than" and "branch if zero (on the result of the set instruction)". Most full-featured assemblers also provide a rich macro language (discussed below) which is used by vendors and programmers to generate more complex code and data sequences.
Each computer architecture has its own machine language. Computers differ in the number and type of operations they support, in the different sizes and numbers of registers, and in the representations of data in storage. While most general-purpose computers are able to carry out essentially the same functionality, the ways they do so differ; the corresponding assembly languages reflect these differences.
Multiple sets of mnemonics or assembly-language syntax may exist for a single instruction set, typically instantiated in different assembler programs. In these cases, the most popular one is usually that supplied by the manufacturer and used in its documentation.
There is a large degree of diversity in the way the authors of assemblers categorize statements and in the nomenclature that they use. In particular, some describe anything other than a machine mnemonic or extended mnemonic as a pseudo-operation (pseudo-op). A typical assembly language consists of 3 types of instruction statements that are used to define program operations:
Instructions (statements) in assembly language are generally very simple, unlike those in high-level languages. Generally, a mnemonic is a symbolic name for a single executable machine language instruction (an opcode), and there is at least one opcode mnemonic defined for each machine language instruction. Each instruction typically consists of an operation or opcode plus zero or more operands. Most instructions refer to a single value, or a pair of values. Operands can be immediate (value coded in the instruction itself), registers specified in the instruction or implied, or the addresses of data located elsewhere in storage. This is determined by the underlying processor architecture: the assembler merely reflects how this architecture works. Extended mnemonics are often used to specify a combination of an opcode with a specific operand, e.g., the System/360 assemblers use
as an extended mnemonic for B
with a mask of 15 and BC
("NO OPeration" – do nothing for one step) for NOP
with a mask of 0.BC
Extended mnemonics are often used to support specialized uses of instructions, often for purposes not obvious from the instruction name. For example, many CPU's do not have an explicit NOP instruction, but do have instructions that can be used for the purpose. In 8086 CPUs the instruction
is used for xchg ax,ax
, with nop
being a pseudo-opcode to encode the instruction nop
. Some disassemblers recognize this and will decode the xchg ax,ax
instruction as xchg ax,ax
. Similarly, IBM assemblers for System/360 and System/370 use the extended mnemonics nop
and NOP
for NOPR
and BC
with zero masks. For the SPARC architecture, these are known as synthetic instructions.[9]BCR
Some assemblers also support simple built-in macro-instructions that generate two or more machine instructions. For instance, with some Z80 assemblers the instruction
is recognized to generate ld hl,bc
followed by ld l,c
.[10] These are sometimes known as pseudo-opcodes.ld h,b
Mnemonics are arbitrary symbols; in 1985 the IEEE published Standard 694 for a uniform set of mnemonics to be used by all assemblers. The standard has since been withdrawn.
There are instructions used to define data elements to hold data and variables. They define the type of data, the length and the alignment of data. These instructions can also define whether the data is available to outside programs (programs assembled separately) or only to the program in which the data section is defined. Some assemblers classify these as pseudo-ops.
Assembly directives, also called pseudo-opcodes, pseudo-operations or pseudo-ops, are instructions that are executed by an assembler at assembly time, not by a CPU at run time. The names of pseudo-ops often start with a dot to distinguish them from machine instructions. Pseudo-ops can make the assembly of the program dependent on parameters input by a programmer, so that one program can be assembled different ways, perhaps for different applications. Or, a pseudo-op can be used to manipulate presentation of a program to make it easier to read and maintain. Another common use of pseudo-ops is to reserve storage areas for run-time data and optionally initialize their contents to known values.
Symbolic assemblers let programmers associate arbitrary names (labels or symbols) with memory locations and various constants. Usually, every constant and variable is given a name so instructions can reference those locations by name, thus promoting self-documenting code. In executable code, the name of each subroutine is associated with its entry point, so any calls to a subroutine can use its name. Inside subroutines, GOTO destinations are given labels. Some assemblers support local symbols which are lexically distinct from normal symbols (e.g., the use of "10$" as a GOTO destination).
Some assemblers, such as NASM provide flexible symbol management, letting programmers manage different namespaces, automatically calculate offsets within data structures, and assign labels that refer to literal values or the result of simple computations performed by the assembler. Labels can also be used to initialize constants and variables with relocatable addresses.
Assembly languages, like most other computer languages, allow comments to be added to program source code that will be ignored during assembly. Judicious commenting is essential in assembly language programs, as the meaning and purpose of a sequence of binary machine instructions can be difficult to determine. The "raw" (uncommented) assembly language generated by compilers or disassemblers is quite difficult to read when changes must be made.
Many assemblers support predefined macros, and others support programmer-defined (and repeatedly re-definable) macros involving sequences of text lines in which variables and constants are embedded. This sequence of text lines may include opcodes or directives. Once a macro has been defined its name may be used in place of a mnemonic. When the assembler processes such a statement, it replaces the statement with the text lines associated with that macro, then processes them as if they existed in the source code file (including, in some assemblers, expansion of any macros existing in the replacement text). Macros in this sense date to IBM autocoders of the 1950s.[citation needed]
This definition of "macro" is slightly different from the use of the term in other contexts, like the C programming language. C macros created through the #define directive are typically just one line, or a few lines at most. Assembler macro instructions can be lengthy "programs" by themselves, executed by interpretation by the assembler during assembly.
Since macros can have 'short' names but expand to several or indeed many lines of code, they can be used to make assembly language programs appear to be far shorter, requiring fewer lines of source code, as with higher level languages. They can also be used to add higher levels of structure to assembly programs, optionally introduce embedded debugging code via parameters and other similar features.
Macro assemblers often allow macros to take parameters. Some assemblers include quite sophisticated macro languages, incorporating such high-level language elements as optional parameters, symbolic variables, conditionals, string manipulation, and arithmetic operations, all usable during the execution of a given macro, and allowing macros to save context or exchange information. Thus a macro might generate numerous assembly language instructions or data definitions, based on the macro arguments. This could be used to generate record-style data structures or "unrolled" loops, for example, or could generate entire algorithms based on complex parameters. An organization using assembly language that has been heavily extended using such a macro suite can be considered to be working in a higher-level language, since such programmers are not working with a computer's lowest-level conceptual elements.
Macros were used to customize large scale software systems for specific customers in the mainframe era and were also used by customer personnel to satisfy their employers' needs by making specific versions of manufacturer operating systems. This was done, for example, by systems programmers working with IBM's Conversational Monitor System / Virtual Machine (VM/CMS) and with IBM's "real time transaction processing" add-ons, Customer Information Control System CICS, and ACP/TPF, the airline/financial system that began in the 1970s and still runs many large computer reservations systems (CRS) and credit card systems today.
It was also possible to use solely the macro processing abilities of an assembler to generate code written in completely different languages, for example, to generate a version of a program in COBOL using a pure macro assembler program containing lines of COBOL code inside assembly time operators instructing the assembler to generate arbitrary code.
This was because, as was realized in the 1960s, the concept of "macro processing" is independent of the concept of "assembly", the former being in modern terms more word processing, text processing, than generating object code. The concept of macro processing appeared, and appears, in the C programming language, which supports "preprocessor instructions" to set variables, and make conditional tests on their values. Note that unlike certain previous macro processors inside assemblers, the C preprocessor was not Turing-complete because it lacked the ability to either loop or "go to", the latter allowing programs to loop.
Despite the power of macro processing, it fell into disuse in many high level languages (major exceptions being C/C++ and PL/I) while remaining a perennial for assemblers.
Macro parameter substitution is strictly by name: at macro processing time, the value of a parameter is textually substituted for its name. The most famous class of bugs resulting was the use of a parameter that itself was an expression and not a simple name when the macro writer expected a name. In the macro:
foo: macro a load a*b
the intention was that the caller would provide the name of a variable, and the "global" variable or constant b would be used to multiply "a". If foo is called with the parameter a-c
, the macro expansion of load a-c*b
occurs. To avoid any possible ambiguity, users of macro processors can parenthesize formal parameters inside macro definitions, or callers can parenthesize the input parameters.[11]
Some assemblers have incorporated structured programming elements to encode execution flow. The earliest example of this approach was in the Concept-14 macro set, originally proposed by Dr. H.D. Mills (March 1970), and implemented by Marvin Kessler at IBM's Federal Systems Division, which extended the S/360 macro assembler with IF/ELSE/ENDIF and similar control flow blocks.[12] This was a way to reduce or eliminate the use of GOTO operations in assembly code, one of the main factors causing spaghetti code in assembly language. This approach was widely accepted in the early '80s (the latter days of large-scale assembly language use).
A curious design was A-natural, a "stream-oriented" assembler for 8080/Z80 processors[citation needed] from Whitesmiths Ltd. (developers of the Unix-like Idris operating system, and what was reported to be the first commercial C compiler). The language was classified as an assembler, because it worked with raw machine elements such as opcodes, registers, and memory references; but it incorporated an expression syntax to indicate execution order. Parentheses and other special symbols, along with block-oriented structured programming constructs, controlled the sequence of the generated instructions. A-natural was built as the object language of a C compiler, rather than for hand-coding, but its logical syntax won some fans.
There has been little apparent demand for more sophisticated assemblers since the decline of large-scale assembly language development.[13] In spite of that, they are still being developed and applied in cases where resource constraints or peculiarities in the target system's architecture prevent the effective use of higher-level languages.[14]
Assembly languages date to the introduction of the stored-program computer. The Electronic Delay Storage Automatic Calculator (EDSAC) (1949) had an assembler called initial orders featuring one-letter mnemonics.[15] Nathaniel Rochester wrote an assembler for an IBM 701 (1954). SOAP (Symbolic Optimal Assembly Program) (1955) was an assembly language for the IBM 650 computer written by Stan Poley.[16]
Assembly languages eliminated much of the error-prone and time-consuming first-generation programming needed with the earliest computers, freeing programmers from tedium such as remembering numeric codes and calculating addresses. They were once widely used for all sorts of programming. However, by the 1980s (1990s on microcomputers), their use had largely been supplanted by higher-level languages, in the search for improved programming productivity. Today assembly language is still used for direct hardware manipulation, access to specialized processor instructions, or to address critical performance issues. Typical uses are device drivers, low-level embedded systems, and real-time systems.
Historically, numerous programs have been written entirely in assembly language. Operating systems were entirely written in assembly language until the introduction of the Burroughs MCP (1961), which was written in Executive Systems Problem Oriented Language (ESPOL), an Algol dialect. Many commercial applications were written in assembly language as well, including a large amount of the IBM mainframe software written by large corporations. COBOL, FORTRAN and some PL/I eventually displaced much of this work, although a number of large organizations retained assembly-language application infrastructures well into the 1990s.
Most early microcomputers relied on hand-coded assembly language, including most operating systems and large applications. This was because these systems had severe resource constraints, imposed idiosyncratic memory and display architectures, and provided limited, buggy system services. Perhaps more important was the lack of first-class high-level language compilers suitable for microcomputer use. A psychological factor may have also played a role: the first generation of microcomputer programmers retained a hobbyist, "wires and pliers" attitude.
In a more commercial context, the biggest reasons for using assembly language were minimal bloat (size), minimal overhead, greater speed, and reliability.
Typical examples of large assembly language programs from this time are IBM PC DOS operating systems and early applications such as the spreadsheet program Lotus 1-2-3. Even into the 1990s, most console video games were written in assembly, including most games for the Mega Drive/Genesis and the Super Nintendo Entertainment System.[citation needed] According to some[who?] industry insiders, the assembly language was the best computer language to use to get the best performance out of the Sega Saturn, a console that was notoriously challenging to develop and program games for.[17] The arcade game NBA Jam (1993) is another example.
Assembly language has long been the primary development language for many popular home computers of the 1980s and 1990s (such as the Sinclair ZX Spectrum, Commodore 64, Commodore Amiga, and Atari ST). This was in large part because interpreted BASIC dialects on these systems offered insufficient execution speed, as well as insufficient facilities to take full advantage of the available hardware on these systems. Some systems even have an integrated development environment (IDE) with highly advanced debugging and macro facilities.
The Assembler for the VIC-20 was written by Don French and published by French Silk. At 1,639 bytes in length, its author believes it is the smallest symbolic assembler ever written. The assembler supported the usual symbolic addressing and the definition of character strings or hex strings. It also allowed address expressions which could be combined with addition, subtraction, multiplication, division, logical AND, logical OR, and exponentiation operators.[18]
There have always been debates over the usefulness and performance of assembly language relative to high-level languages. Assembly language has specific niche uses where it is important; see below. Assembler can be used to optimize for speed or optimize for size. In the case of speed optimization, modern optimizing compilers are claimed[19] to render high-level languages into code that can run as fast as hand-written assembly, despite the counter-examples that can be found.[20][21][22] The complexity of modern processors and memory sub-systems makes effective optimization increasingly difficult for compilers, as well as assembly programmers.[23][24] Moreover, increasing processor performance has meant that most CPUs sit idle most of the time,[25] with delays caused by predictable bottlenecks such as cache misses, I/O operations and paging. This has made raw code execution speed a non-issue for many programmers.
There are some situations in which developers might choose to use assembly language:
Assembly language is still taught in most computer science and electronic engineering programs. Although few programmers today regularly work with assembly language as a tool, the underlying concepts remain very important. Such fundamental topics as binary arithmetic, memory allocation, stack processing, character set encoding, interrupt processing, and compiler design would be hard to study in detail without a grasp of how a computer operates at the hardware level. Since a computer's behavior is fundamentally defined by its instruction set, the logical way to learn such concepts is to study an assembly language. Most modern computers have similar instruction sets. Therefore, studying a single assembly language is sufficient to learn: I) the basic concepts; II) to recognize situations where the use of assembly language might be appropriate; and III) to see how efficient executable code can be created from high-level languages. [29] This is analogous to children needing to learn the basic arithmetic operations (e.g., long division), although calculators are widely used for all except the most trivial calculations.
For any given personal computer, mainframe, embedded system, and game console, both past and present, at least one – possibly dozens – of assemblers have been written. For some examples, see the list of assemblers.
On Unix systems, the assembler is traditionally called as, although it is not a single body of code, being typically written anew for each port. A number of Unix variants use GAS.
Within processor groups, each assembler has its own dialect. Sometimes, some assemblers can read another assembler's dialect, for example, TASM can read old MASM code, but not the reverse. FASM and NASM have similar syntax, but each support different macros that could make them difficult to translate to each other. The basics are all the same, but the advanced features will differ.[35]
Also, assembly can sometimes be portable across different operating systems on the same type of CPU. Calling conventions between operating systems often differ slightly or not at all, and with care it is possible to gain some portability in assembly language, usually by linking with a C library that does not change between operating systems.[citation needed] An instruction set simulator can process the object code/ binary of any assembler to achieve portability even across platforms with an overhead no greater than a typical bytecode interpreter.[citation needed] This is similar to use of microcode to achieve compatibility across a processor family.
Some higher level computer languages, such as C and Borland Pascal, support inline assembly where sections of assembly code, in practice usually brief, can be embedded into the high level language code. The Forth language commonly contains an assembler used in CODE words.
An emulator can be used to debug assembly-language programs.
The following is a partial listing generated by the NASM, an assembler for 32-bit Intel x86 CPUs. The code is for a subroutine, not a complete program.
100 ;-----------------------------------------------------------
101 ; zstr_count:
102 ; Counts a zero-terminated ASCII string to determine its size
103 ; in: eax = start address of the zero terminated string
104 ; out: ecx = count = the length of the string
105
106 zstr_count: ; Entry point
107 00000030 B9FFFFFFFF mov ecx, -1 ; Init the loop counter, pre-decrement
108 ; to compensate for the increment
109 .loop:
110 00000035 41 inc ecx ; Add 1 to the loop counter
111 00000036 803C0800 cmp byte [eax + ecx], 0 ; Compare the value at the string's
112 ; [starting memory address Plus the
113 ; loop offset], to zero
114 0000003A 75F9 jne .loop ; If the memory value is not zero,
115 ; then jump to the label called '.loop',
116 ; otherwise continue to the next line
117 .done:
118 ; We don't do a final increment,
119 ; because even though the count is base 1,
120 ; we do not include the zero terminator in the
121 ; string's length
122 0000003C C3 ret ; Return to the calling program
The first column (from the left) is simply the line number in the listing and is otherwise meaningless. The second column is the relative address, in hex, of where the code will be placed in memory. The third column is the actual compiled code. For instance,
is the x86 opcode for the B9
MOV
instruction; ECX
is the value −1 in two's-complement binary form.FFFFFFFF
Names suffixed with colons (
) are symbolic labels; the labels do not create code, they are simply a way to tell the assembler that those locations have symbolic names. The :
label is only present for clarity of where the program ends, it does not serve any other purpose. Prefixing a period (.done
) on a label is a feature of the assembler, declaring the label as being local to the subroutine..
Computer programming portal | |
Computer science portal |
C++ was primarily designed so that the author and his friends would not have to program in assembler, C, or various modern high-level languages. [use of the term assembler to mean assembly language]
|url=
(help)
Find more about
Assembly language |
|
Definitions from Wiktionary | |
Media from Commons | |
News stories from Wikinews | |
Textbooks from Wikibooks | |
Learning resources from Wikiversity |
|
|
全文を閲覧するには購読必要です。 To read the full text you will need to subscribe.
関連記事 | 「assembly」 |
.