メニュー English Ukrainian ロシア語 ホーム

愛好家や専門家向けの無料テクニカル ライブラリ 無料のテクニカルライブラリ


情報学と情報技術。 講義ノート: 簡単に言うと、最も重要なこと

講義ノート、虎の巻

ディレクトリ / 講義ノート、虎の巻

記事へのコメント 記事へのコメント

目次

  1. コンピューターサイエンス入門 (情報学。情報。情報の表現と処理。数値システム。コンピューターにおける数値の表現。アルゴリズムの形式化された概念)
  2. パスカル言語 (Pascal の概要。標準的なプロシージャと関数。Pascal 演算子)
  3. 手順と機能 (補助アルゴリズムの概念。Pascal の手順。Pascal の関数。予測的な記述とサブルーチンの接続。ディレクティブ)
  4. サブルーチン (ルーチンパラメータ。サブルーチンパラメータの型。Pascalの文字列型。文字列型変数の手続きと関数。レコード。セット)
  5. ファイル (ファイル。ファイル操作。モジュール。モジュールの種類)
  6. ダイナミックメモリ (参照データ型。動的メモリ。動的変数。動的メモリの操作。型なしポインタ)
  7. 抽象的なデータ構造 (抽象データ構造、スタック、キュー)
  8. ツリーデータ構造 (ツリーデータ構造。ツリーに対する操作。操作の実装例)
  9. カウント (グラフの概念。グラフを表現する方法。出現率のリストによるグラフの表現。グラフの深さ優先探索アルゴリズム。リストのリストとしてのグラフの表現。グラフの幅優先探索アルゴリズム。 )
  10. オブジェクトのデータ型 (Pascal のオブジェクト タイプ。オブジェクトの概念、その説明と使用法。継承。オブジェクトのインスタンスの作成。コンポーネントとスコープ)
  11. メソッド (メソッド。コンストラクターとデストラクター。デストラクター。仮想メソッド。オブジェクト データ フィールドと仮メソッド パラメーター)
  12. オブジェクト タイプの互換性 (カプセル化。拡張可能なオブジェクト。オブジェクト タイプの互換性)
  13. アセンブラ (アセンブラについて。マイクロプロセッサのソフトウェアモデル。ユーザーレジスタ。汎用レジスタ。セグメントレジスタ。ステータスレジスタ、コントロールレジスタ)
  14. レジスタ (マイクロプロセッサシステムレジスタ。制御レジスタ。システムアドレスレジスタ。デバッグレジスタ)
  15. 組み立てプログラム (アセンブラプログラムの構造。アセンブラ構文。比較演算子。演算子とその優先順位。簡略化されたセグメント定義ディレクティブ。MODEL ディレクティブによって作成された識別子。メモリモデル。メモリモデル修飾子)
  16. 組立説明書の構造 (機械命令の構造、命令のオペランドの指定方法、アドレッシング方法)
  17. チーム (データ転送コマンド、演算コマンド)
  18. 制御転送コマンド (論理コマンド。論理否定の真理値表。論理包含ORの真理値表。論理ANDの真理値表。排他的ORの真理値表。jccコマンド名の略語の意味。コマンドの条件ジャンプコマンド一覧。条件ジャンプコマンドとフラグ)

LECTURE No. 1. コンピュータサイエンス入門

1.コンピュータサイエンス。 情報。 情報の表現と処理

情報学は、科学、技術、生産のさまざまな分野で、オブジェクトとそれらの関係の構造の形式化された表現に従事しています。 論理式、データ構造、プログラミング言語など、さまざまな正式なツールを使用してオブジェクトや現象をモデル化します。

コンピューター サイエンスでは、情報などの基本的な概念にはさまざまな意味があります。

1)外部形式の情報の正式な提示。

2)情報の抽象的な意味、その内部コンテンツ、セマンティクス。

3)情報と実世界との関係。

しかし、原則として、情報はその抽象的な意味、つまりセマンティクスとして理解されます。 情報の表現を解釈することにより、その意味、セマンティクスを取得します。 したがって、情報を交換したい場合は、解釈の正しさを損なわないように一貫した見方が必要です。 これを行うために、情報の表現の解釈は、いくつかの数学的構造で識別されます。 この場合、情報処理は厳密な数学的方法で実行できます。

情報の数学的記述の 1 つは、関数 y =f(x, t) の形式での情報の表現です。ここで、t は時間、x は y の値が測定される特定のフィールド内の点です。カイ関数のパラメータに応じて(情報を分類できます。)

パラメータが連続した一連の値をとるスカラー量である場合、この方法で得られた情報は連続 (またはアナログ) と呼ばれます。 パラメータに特定の変化ステップが与えられている場合、その情報は離散的と呼ばれます。 特定のパラメータごとに、特定の精度で関数値を取得できるため、離散情報は普遍的であると見なされます。

離散情報は通常、デジタル情報で識別されます。これは、アルファベット表現の記号情報の特殊なケースです。 アルファベットは、あらゆる性質の記号の有限集合です。 コンピューター サイエンスでは、あるアルファベットの文字を別の文字で表現しなければならない、つまりエンコード操作を実行する必要がある場合がよくあります。 エンコーディング アルファベットの文字数がエンコーディング アルファベットの文字数よりも少ない場合、エンコーディング操作自体は複雑ではありません。

実践でわかるように、他のアルファベットをエンコードできる最も単純なアルファベットは、通常 0 と 1 で表される 2 つの文字で構成されるバイナリです。バイナリ アルファベットの n 文字を使用すると、XNUMXn 文字をエンコードでき、これは任意のアルファベットをエンコードするには十分です。

8進数のアルファベットの記号で表現できる値を情報の最小単位またはビットと呼びます。 256 ビットのシーケンス - バイト。 8 の異なる XNUMX ビット シーケンスを含むアルファベットは、バイト アルファベットと呼ばれます。

今日の計算機科学の標準として、各文字を 1 バイトでエンコードするコードが採用されています。 他にもアルファベットがあります。

2. 番号システム

記数法は、番号の命名と書き込みに関する一連の規則です。 位置番号システムと非位置番号システムがあります。

数の桁の値が数の桁の位置に依存する場合、数システムは定位置と呼ばれます。 それ以外の場合は、非定位置と呼ばれます。 数値の値は、数値内のこれらの桁の位置によって決まります。

3. コンピュータにおける数の表現

32ビットプロセッサは最大232-1RAMで動作し、アドレスは00000000〜FFFFFFFFの範囲で書き込むことができます。 ただし、リアルモードでは、プロセッサは最大220-1のメモリで動作し、アドレスは00000〜FFFFFの範囲になります。 メモリのバイトは、固定長と可変長の両方のフィールドに組み合わせることができます。 ワードは2バイトで構成される固定長フィールドであり、ダブルワードは4バイトのフィールドです。 フィールドアドレスは偶数でも奇数でもかまいませんが、偶数アドレスの方が高速です。

固定小数点数は、コンピューターでは整数の1進数として表され、サイズは2、4、またはXNUMXバイトです。

2 進整数は 10 の補数で表され、固定小数点数は XNUMX の補数で表されます。さらに、数値が XNUMX バイトを占める場合、数値の構造は次の規則に従って書き込まれます。最上位桁は数値の符号に割り当てられ、残りは数値の XNUMX 進数に割り当てられます。正の数の補数コードはその数自体に等しく、負の数の補数コードは次の式を使用して取得できます: x = XNUMXi - \x\ (n は数の桁数)。

XNUMX 進数システムでは、追加のコードは、ビットを反転することによって得られます。

仮数のビット数は数値の表現の精度を決定し、マシンオーダービットの数は浮動小数点数の表現の範囲を決定します。

4.アルゴリズムの形式化された概念

アルゴリズムは、同時に何らかの数学的対象が存在する場合にのみ存在できます。 アルゴリズムの形式化された概念は、再帰関数、通常のマルコフアルゴリズム、チューリングマシンの概念に関連しています。

数学では、引数の任意のセットに対して、関数の一意の値が決定される法則がある場合、関数は単一値と呼ばれます。 アルゴリズムはそのような法則として機能します。 この場合、関数は計算可能であると言われます。

再帰関数は計算可能関数のサブクラスであり、計算を定義するアルゴリズムはコンピュテーション再帰関数アルゴリズムと呼ばれます。 まず、基本的な再帰関数が固定されており、付随するアルゴリズムは自明で明確です。 次に、置換、再帰、最小化のXNUMXつのルールが導入され、基本関数に基づいてより複雑な再帰関数が取得されます。

基本的な機能とそれに付随するアルゴリズムは次のとおりです。

1) n 個の独立変数の関数で、ゼロに等しくなります。 次に、関数の符号が φn の場合、引数の数に関係なく、関数の値はゼロに設定する必要があります。

2) 形式 ψni の n 個の独立変数の恒等関数。 次に、関数の符号が ψni の場合、関数の値は、左から右に数えて i 番目の引数の値と見なされます。

3) Λ は XNUMX つの独立した引数の関数です。 次に、関数の符号が λ の場合、関数の値は、引数の値に続く値と見なされます。 さまざまな学者が、形式化されたものへの独自のアプローチを提案しています。

アルゴリズムの表現。 たとえば、アメリカの科学者チャーチは、計算可能な関数のクラスは再帰関数によって使い果たされ、その結果、非負の整数のセットを別のセットに処理するアルゴリズムが何であれ、再帰関数に付随するアルゴリズムが存在することを示唆しました。与えられたものに相当します。 したがって、特定の問題を解決するための再帰関数を作成できない場合、それを解決するためのアルゴリズムはありません。 別の科学者であるチューリングは、一連の入力文字を処理して出力する仮想コンピューターを開発しました。 この点に関して、彼は、計算可能な関数はすべてチューリング計算可能であるという論文を提唱しました。

講義2。Pascal言語

1. Pascal 言語の紹介

言語の基本記号 - 文字、数字、特殊文字 - がアルファベットを構成しています。 Pascal 言語には、次の基本的な記号のセットが含まれています。

1) 26 個のラテン小文字と 26 個のラテン大文字:

ABCDEFGHIJKLMNOPQRSTUVWXYZ

abcdefghijklmnopqrstuvwxyz;

2)_(アンダースコア);

3) 10 桁: 0123456789;

4) 操作の兆候:

+ - x / = <> < > <= >= := @;

5) リミッター:

., ' ( ) [ ] (..) { } (* *).. : ;

6) 指定子: ^ # $;

7)サービス(予約)語:

ABSOLUTE、アセンブラ、AND、配列、ASM、BEGIN、CASE、CONST、コンストラクタ、デストラクタ、DIV、DO、DOWNTO、ELSE、END、EXPORT、EXTERNAL、FAR、FILE、FOR、FORWARD、FUNCTION、GOTO、IF、実装、 IN、INDEX、INHERITED、INLINE、INTERFACE、INTERRUPT、LABEL、LIBRARY、MOD、NAME、NIL、NEAR、NOT、OBJECT、OF、OR、PACKED、PRIVATE、PROCEDURE、PROGRAM、PUBLIC、RECORD、REPEAT、RESIDENT、SET、 SHL、SHR、STRING、THEN、TO、TYPE、UNIT、UNTIL、USES、VAR、VIRTUAL、WHILE、WITH、XOR。

リストされているものに加えて、基本文字のセットにはスペースが含まれています。 二重文字および予約語内にスペースを使用することはできません。

データのタイプの概念

数学では、いくつかの重要な特性に従って変数を分類するのが通例です。 実変数、複素変数、論理変数の間、個々の値を表す変数と一連の値などの間で厳密な区別が行われます。コンピューターでデータを処理する場合、そのような分類はさらに重要です。 どのアルゴリズム言語でも、すべての定数、変数、式、または関数は特定の型です。

Pascal には規則があります。型は、変数または関数を使用する前に宣言するときに明示的に指定されます。 Pascal 型の概念には、次の主要なプロパティがあります。

1)任意のデータ型は、定数が属する値のセット、変数または式が取ることができる値、または操作または関数が生成できる値のセットを定義します。

2) 定数、変数、または式によって与えられる値の型は、それらの形式または記述によって決定できます。

3)各操作または関数は固定型の引数を必要とし、固定型の結果を生成します。

したがって、コンパイラは型情報を使用して、さまざまな構造の計算可能性と正確性をチェックできます。

タイプは次を定義します。

1) 与えられた型に属する変数、定数、関数、式の可能な値;

2) コンピュータにおけるデータ表示の内部形式。

3) 特定の型に属する値に対して実行できる操作と関数。

タイプの必須の説明はプログラムのテキストの冗長性につながることに注意する必要がありますが、そのような冗長性はプログラムを開発するための重要な補助ツールであり、現代の高レベルアルゴリズム言語の必要なプロパティと見なされます。

Pascalにはスカラーデータ型と構造化データ型があります。 スカラータイプには、標準タイプとユーザー定義タイプが含まれます。 標準タイプには、整数、実数、文字、ブール、およびアドレスタイプが含まれます。

整数型は、特定のコンピューターで許可されている整数のセットによって値が実現される定数、変数、および関数を定義します。

実数型は、特定のコンピューターで許可されている実数のサブセットによって実装されるデータを定義します。

ユーザー定義型は enum と range です。 構造化型には、配列、セット、レコード、およびファイルの XNUMX つの種類があります。

リストされているものに加えて、Pascalには手続き型とオブジェクトのXNUMXつのタイプがあります。

言語式は、定数、変数、関数ポインター、演算子記号、および角かっこで構成されます。 式は、値を計算するためのルールを定義します。 計算の順序は、それに含まれる操作の優先度 (優先度) によって決まります。 Pascal の演算子の優先順位は次のとおりです。

1) 括弧内の計算;

2)関数値の計算。

3) 単項演算。

4) 操作 *、/、div、mod、および;

5) 操作 +、-、または xor。

6)関係演算=、<>、<、>、<=、>=。

式は多くのPascal言語演算子の一部であり、組み込み関数の引数にすることもできます。

2.標準的な手順と機能

算術関数

1.Function Abs(X);

パラメータの絶対値を返します。

X は、実数または整数型の式です。

2. 関数 ArcTan (X: 拡張): 拡張;

引数の逆正接を返します。

X は、実数または整数型の式です。

3.関数exp(X:実数):実数;

指数を返します。

X は、実数または整数型の式です。

4.Frac(X: 実数): 実数;

引数の小数部分を返します。

X は実数型式です。 結果は X の小数部、つまり

Frac(X)= X-Int(X)。

5. 関数 Int(X: 実数): 実数;

引数の整数部分を返します。

X は実数型式です。 結果は X の整数部分、つまり X はゼロに向かって丸められます。

6. 関数 Ln(X: 実数): 実数;

実数型式 X の自然対数 (Ln e = 1) を返します。

7.機能Pi:拡張;

3.1415926535として定義されている円周率の値を返します。

8.関数Sin(X:拡張):拡張;

引数のサインを返します。

X は実数型式です。 Sin は、角度 X のサインをラジアンで返します。

9. 関数 Sqr (X: 拡張): 拡張;

引数の二乗を返します。

Xは浮動小数点式です。 結果はXと同じタイプです。

10.Function Sqrt(X: 拡張): 拡張;

引数の平方根を返します。

X は浮動小数点式です。 結果は X の平方根です。

値変換の手順と機能

1. 手続き Str(X [: Width [: Decimals]]; var S);

に従って、数値 X を文字列表現に変換します。

幅と小数のフォーマットオプション。 Xは、実数型または整数型の式です。 幅と小数は整数型の式です。 Sは、String型の変数、または拡張構文が許可されている場合はnullで終了する文字配列です。

2. 関数 Chr(X: バイト): Char;

ASCII テーブルで序数が X の文字を返します。

3.機能高(X);

パラメータの範囲内の最大値を返します。

4.FunctionLow(X);

パラメータ範囲の最小値を返します。

5 FunctionOrd(X):Longint;

列挙型式の序数を返します。 Xは列挙型式です。

6. 関数 Round(X: 拡張): 倍長整数;

実数型の値を最も近い整数に丸めます。 X は実数型式です。 Round は倍長整数値を返します。これは、最も近い整数に丸められた X の値です。 X が XNUMX つの整数のちょうど中間にある場合、絶対値が最大の数値が返されます。 X の丸められた値が倍長整数の範囲外である場合、EInvalidOp 例外を使用して処理できる実行時エラーが生成されます。

7.関数Trunc(X:拡張):Longint;

実数型の値を整数に切り捨てます。 Xの丸められた値がLongintの範囲外の場合、EInvalidOp例外を使用して処理できるランタイムエラーが生成されます。

8. 手順 Val(S; var V; var Code: Integer);

数値を文字列値Sから数値に変換します

表現 V. S - 文字列型式 - 整数または実数を形成する一連の文字。 S 式が無効な場合、無効な文字のインデックスが Code 変数に格納されます。 それ以外の場合、Code はゼロに設定されます。

順序値の手順と関数

1.プロシージャDec(varX [; N:LongInt]);

変数Xから1またはNを減算します。Dec(X)はX:= X-XNUMXに対応し、Dec(X、N)はX:=X-Nに対応します。Xは列挙型または型の変数です。拡張構文が許可され、Nが整数型の式である場合はPChar。 Decプロシージャは最適なコードを生成し、長いループで特に役立ちます。

2.プロシージャInc(varX [; N:LongInt]);

変数 X に 1 または N を加算します。X は、拡張構文が許可されている場合は列挙型または PChar 型の変数であり、N は整数型の式です。 Inc (X) は命令 X:= X + XNUMX に一致し、Inc (X, N) は命令 X:= X + N に一致します。Inc プロシージャは最適なコードを生成し、特に長いループで役立ちます。

3. FunctionOdd(X: LongInt): ブール値;

X が奇数の場合は True、そうでない場合は False を返します。

4.FunctionPred(X);

パラメータの以前の値を返します。 X は列挙型式です。 結果は同じタイプです。

5 関数 Succ(X);

次のパラメータ値を返します。 Xは列挙型式です。 結果は同じタイプです。

3. Pascal 言語の演算子

条件演算子

完全な条件ステートメントの形式は次のように定義されます。 ここで、B は分岐条件 (意思決定)、論理式、または関係です。 SI、S2 - 2 つの実行可能なステートメント、単純または複合。

条件ステートメントを実行するときは、最初に式Bが評価され、次にその結果が分析されます。Bがtrueの場合、ステートメントS1が実行されます(thenの分岐)。ステートメントS2はスキップされます。 Bがfalseの場合、ステートメントS2-elseブランチが実行され、ステートメントS1はスキップされます。

条件演算子の省略形もあります。 それは次のように書かれます: もし B なら S.

選択演算子

演算子の構造は次のとおりです。

ケースSの

c1: 命令 1;

c2: 命令 2;

...

cn: 命令N;

その他の命令

終わり

ここで、S は、値が計算される序数型の式です。

с1、с2...、сп - 式が比較される序数型の定数

S;命令 1,..., 命令 N - 定数が式 S の値と一致する演算子が実行されます。

命令 - Sylq 式の値が定数 c1、c2.... cn のいずれにも一致しない場合に実行されるステートメント。

この演算子は、任意の数の選択肢に対する条件付きIf演算子を一般化したものです。 他に分岐がないステートメントの省略形があります。

パラメータ付きのループステートメント

forという単語で始まるパラメーター・ループ・ステートメントにより、複合ステートメントである可能性のあるステートメントが、制御変数に値の昇順のシーケンスが割り当てられている間、繰り返し実行されます。

for ステートメントの一般的なビュー:

for <ループカウンター> := <開始値> to <終了値> do <ステートメント>;

for ステートメントの実行が開始されると、開始値と終了値が一度決定され、これらの値は for ステートメントの実行中ずっと保持されます。 for ステートメントの本体に含まれるステートメントは、開始値と終了値の間の範囲内の値ごとに XNUMX 回実行されます。 ループカウンタは常に初期値で初期化されます。 for ステートメントの実行中、ループ カウンターの値は反復ごとにインクリメントされます。 開始値が終了値より大きい場合、for ステートメントの本体に含まれるステートメントは実行されません。 downto キーワードがループ ステートメントで使用されると、制御変数の値は反復ごとに XNUMX ずつ減分されます。 このようなステートメントの開始値が終了値より小さい場合、ループ ステートメントの本体に含まれるステートメントは実行されません。

for ステートメントの本体に含まれるステートメントがループ カウンターの値を変更する場合、これはエラーです。 for ステートメントの実行後、 for ステートメントの実行が jump ステートメントによって中断されない限り、制御変数の値は未定義になります。

前提条件付きループ文

前提条件ループ ステートメント (while キーワードで始まる) には、ステートメント (複合ステートメントの場合もある) の繰り返し実行を制御する式が含まれています。 サイクル形状:

BがSを行う間;

ここで、B は論理条件であり、その真偽がチェックされます (ループを終了するための条件です)。

S - ループ本体 - XNUMX つのステートメント。

ステートメントの繰り返しを制御する式は、ブール型である必要があります。 内部ステートメントが実行される前に評価されます。 式がTrueと評価される限り、内部ステートメントは繰り返し実行されます。 式が最初からFalseと評価された場合、前提条件ループステートメントに含まれるステートメントは実行されません。

事後条件付きのループステートメント

事後条件付きのループステートメント(repeatという単語で始まる)では、一連のステートメントの繰り返し実行を制御する式が、repeatステートメント内に含まれています。 サイクル形状:

B まで S を繰り返します。

ここで、Bは論理条件であり、その真偽がチェックされます(ループを終了するための条件です)。

S - XNUMX つ以上のループ本体ステートメント。

式の結果はブール型である必要があります。 繰り返しキーワードとuntilキーワードで囲まれたステートメントは、式の結果がTrueと評価されるまで順番に実行されます。 式はステートメントシーケンスの実行ごとに評価されるため、ステートメントシーケンスは少なくともXNUMX回実行されます。

LECTURE № 3. 手順と機能

1. 補助アルゴリズムの概念

問題を解決するためのアルゴリズムは、問題全体を個別のサブタスクに分解することによって設計されます。 通常、サブタスクはサブルーチンとして実装されます。

サブルーチンは、パラメーターと呼ばれるいくつかの入力量のさまざまな値を使用して、メイン アルゴリズムで繰り返し使用される補助アルゴリズムです。

プログラミング言語のサブルーチンは、プログラム内のXNUMXつの場所でのみ定義および記述される一連のステートメントですが、プログラム内のXNUMXつ以上のポイントから実行するために呼び出すことができます。 各サブルーチンは一意の名前で識別されます。

Pascalには、プロシージャと関数のXNUMX種類のサブルーチンがあります。 プロシージャと関数は、宣言とステートメントの名前付きシーケンスです。 プロシージャまたは関数を使用する場合、プログラムには、プロシージャまたは関数のテキストと、プロシージャまたは関数の呼び出しが含まれている必要があります。 説明で指定されたパラメーターは形式的と呼ばれ、サブルーチンの呼び出しで指定されたパラメーターは実際と呼ばれます。 すべての正式なパラメータは、次のカテゴリに分類できます。

1)パラメータ-変数;

2) 定数パラメータ。

3) パラメータ値。

4) 手続きパラメータと関数パラメータ、つまり手続き型パラメータ。

5) 型指定されていない変数パラメーター。

手続きと関数のテキストは、手続きと関数の説明のセクションに配置されます。

プロシージャ名と関数名をパラメータとして渡す

多くの問題、特に計算数学では、手続きと関数の名前をパラメータとして渡す必要があります。 これを行うために、TURBO PASCAL は、記述内容に応じて手続き型または関数型の新しいデータ型を導入しました。 (手続き型と関数型は、型宣言のセクションで説明されています。)

関数と手続き型は、手続きの見出しと、正式なパラメーターのリストはあるが名前がない関数として定義されます。 パラメータなしで関数または手続き型を定義することができます。次に例を示します。

type

Proc=プロシージャ;

手続き型または関数型を宣言した後、それを使用して、正式なパラメーター(手続きおよび関数の名前)を記述することができます。 さらに、名前が実際のパラメーターとして渡される実際のプロシージャーまたは関数を作成する必要があります。

2.パスカルでの手順

各プロシージャの説明には、見出しとそれに続くプログラムブロックが含まれます。 プロシージャヘッダーの一般的な形式は次のとおりです。

プロシージャ<名前>[(<正式なパラメータのリスト>)];

プロシージャーは、プロシージャーの名前と必要なパラメーターを含むプロシージャー・ステートメントで活動化されます。 プロシージャーの実行時に実行されるステートメントは、プロシージャー・モジュールのステートメント部分に含まれています。 プロシージャに含まれるステートメントがプロシージャ モジュール内のプロシージャ識別子を使用する場合、プロシージャは再帰的に実行されます。つまり、実行時にそれ自体を参照します。

3.Pascalの機能

関数宣言は、値が計算されて返されるプログラムの部分を定義します。 関数ヘッダーの一般的な形式は次のとおりです。

Function <名前> [(<仮パラメータのリスト>)]: <戻り値の型>;

関数は呼び出されるとアクティブ化されます。 関数が呼び出されると、関数識別子とその評価に必要なパラメーターが指定されます。 関数呼び出しは、オペランドとして式に含めることができます。 式が評価されると、関数が実行され、オペランドの値が関数によって返される値になります。

関数ブロックの演算子部分は、関数がアクティブ化されたときに実行する必要があるステートメントを指定します。 モジュールには、関数識別子に値を割り当てる割り当てステートメントが少なくとも XNUMX つ含まれている必要があります。 関数の結果は、最後に割り当てられた値です。 そのような代入ステートメントがない場合、または実行されていない場合、関数の戻り値は未定義です。

モジュール内の関数を呼び出すときに関数識別子が使用される場合、その関数は再帰的に実行されます。

4.サブルーチンの説明と接続を転送します。 指令

プログラムには複数のサブルーチンが含まれる場合があります。つまり、プログラムの構造が複雑になる場合があります。 ただし、これらのサブルーチンは同じネストレベルにすることができるため、特別な前方宣言が使用されていない限り、サブルーチン宣言を最初に実行してから、サブルーチン宣言を呼び出す必要があります。

ステートメントブロックの代わりに前方ディレクティブを含むプロシージャ宣言は、前方宣言と呼ばれます。 この宣言の後のある時点で、定義宣言を使用してプロシージャを定義する必要があります。 定義宣言は、同じプロシージャーIDを使用しますが、仮パラメーターのリストを省略し、ステートメント・ブロックを含む宣言です。 前方宣言と定義宣言は、プロシージャ宣言と関数宣言の同じ部分に含まれている必要があります。 それらの間で、前方宣言プロシージャを参照できる他のプロシージャと関数を宣言できます。 したがって、相互再帰が可能です。

前方記述と定義記述は、手順の完全な記述です。 手順は、前方記述を使用して記述されていると見なされます。

プログラムに非常に多くのサブルーチンが含まれている場合、プログラムは視覚的ではなくなり、ナビゲートするのが難しくなります。 これを回避するために、一部のルーチンはソース ファイルとしてディスクに保存され、必要に応じて、コンパイル ディレクティブを使用してコンパイル段階でメイン プログラムに接続されます。

ディレクティブは、プログラム内の通常のコメントが配置できる場所ならどこにでも配置できる特別なコメントです。 ただし、ディレクティブには特別な表記法があるという点で異なります。スペースなしの閉じ括弧の直後に S 記号が書き込まれ、再びスペースなしでディレクティブが示されます。

1) {SE+} - 演算コプロセッサをエミュレートします。

2){SF+}-遠隔タイプのプロシージャと関数呼び出しを形成します。

3){SN+}-数学コプロセッサーを使用します。

4) {SR+} - 範囲が範囲外かどうかを確認します。

一部のコンパイル スイッチには、次のようなパラメーターが含まれる場合があります。

{$1 file name} - 指定されたファイルをコンパイル済みプログラムのテキストに含めます。

LECTURE No. 4. サブルーチン

1. サブプログラムのパラメータ

プロシージャまたは関数の説明は、正式なパラメータのリストを指定します。 正式なパラメータリストで宣言された各パラメータは、記述されたプロシージャまたは関数に対してローカルであり、そのプロシージャまたは関数に関連付けられたモジュールでその識別子によって参照できます。

パラメータには、値、変数、型なし変数のXNUMX種類があります。 それらは次のように特徴づけられます。

1. 前にキーワードがないパラメーターのグループは、値パラメーターのリストです。

2. const キーワードが前にあり、型が後に続くパラメーターのグループは、定数パラメーターのリストです。

3. varキーワードが前にあり、その後にタイプが続くパラメーターのグループは、タイプされていない変数パラメーターのリストです。

4. varまたはconstキーワードが前にあり、タイプが後にないパラメーターのグループは、型なし変数パラメーターのリストです。

2. サブルーチンのパラメータの種類

値パラメーター

仮値パラメーターは、プロシージャーまたは関数が呼び出されたときに対応する実パラメーターから初期値を導出することを除いて、プロシージャーまたは関数に対してローカルな変数として扱われます。 仮の値パラメーターが受ける変更は、実際のパラメーターの値には影響しません。 対応する実際の値パラメーター値は式である必要があり、その値はファイル タイプまたはファイル タイプを含む構造タイプであってはなりません。

実際のパラメーターは、仮値パラメーターのタイプと割り当て互換性のあるタイプである必要があります。 パラメータが文字列型の場合、仮パラメータのサイズ属性は255になります。

定数パラメータ

仮定数パラメーターは、対応する実パラメーターからプロシージャーまたは関数が呼び出されたときにその値を取得する読み取り専用のローカル変数と同様に機能します。 仮定数パラメーターへの代入は許可されていません。 また、仮定数パラメーターを実パラメーターとして別のプロシージャーまたは関数に渡すこともできません。 プロシージャまたは関数ステートメントの実パラメータに対応する定数パラメータは、実パラメータ値と同じ規則に従う必要があります。

プロシージャまたは関数の実行中に仮パラメータの値が変更されない場合は、値パラメータの代わりに定数パラメータを使用する必要があります。 定数パラメーターを使用すると、プロシージャまたは関数を実装して、仮パラメーターへの偶発的な割り当てを防ぐことができます。 さらに、構造体および文字列型パラメーターの場合、定数パラメーターの値パラメーターの代わりに使用すると、コンパイラーはより効率的なコードを生成できます。

可変パラメータ

変数パラメーターは、値をプロシージャーまたは関数から呼び出し側プログラムに渡す必要がある場合に使用されます。 プロシージャまたは関数呼び出しステートメントの対応する実パラメータは、変数参照である必要があります。 プロシージャまたは関数が呼び出されると、仮パラメータ変数は実際の変数に置き換えられ、仮パラメータ変数の値の変更は実際のパラメータに反映されます。

プロシージャまたは関数内で仮変数パラメータを参照すると、実パラメータ自体にアクセスすることになります。 実パラメータの型は、仮変数パラメータの型と一致する必要がありますが、この制限は、型なし変数パラメータを使用することで回避できます)。

型なしパラメータ

仮パラメータが型指定されていない変数パラメータである場合、対応する実パラメータは、その型に関係なく、変数または定数への任意の参照にすることができます。 var キーワードで宣言された型なしパラメーターは変更できますが、const キーワードで宣言された型なしパラメーターは読み取り専用です。

プロシージャまたは関数では、型指定されていない変数パラメータには型がありません。つまり、変数型の割り当てによって特定の型が指定されるまで、すべての型の変数と互換性がありません。

型指定されていないパラメーターはより柔軟性がありますが、それらの使用に関連するいくつかのリスクがあります。 コンパイラーは、型指定されていない変数に対する操作の有効性をチェックできません。

手続き変数

手続き型を定義すると、この型の変数を記述できるようになります。 このような変数は手続き型変数と呼ばれます。 整数型の値を割り当てることができる整数変数と同様に、手続き型変数には手続き型の値を割り当てることができます。 もちろん、そのような値は別のプロシージャ変数である可能性がありますが、プロシージャまたは関数の識別子である可能性もあります。 このコンテキストでは、プロシージャまたは関数の宣言は、値がプロシージャまたは関数である特別な種類の定数の記述と見なすことができます。

他の割り当てと同様に、左側と右側の変数の値は割り当て互換でなければなりません。 代入の互換性を保つ手続き型には、同じ数のパラメーターが必要であり、対応する位置のパラメーターは同じ型でなければなりません。 手続き型宣言のパラメーター名は効果がありません。

さらに、割り当ての互換性を確保するために、プロシージャまたは関数がプロシージャ変数に割り当てられる場合は、次の要件を満たす必要があります。

1) 標準的な手順または関数であってはなりません。

2) そのようなプロシージャまたは関数はネストできません。

3) そのような手続きはインライン手続きであってはなりません。

4) 割り込み手続きであってはなりません。

標準手続きと関数は、Writeln、Readln、Chr、Ord など、System モジュールで記述された手続きと関数です。 プロシージャ変数を持つネストされたプロシージャおよび関数は使用できません。 プロシージャまたは関数は、別のプロシージャまたは関数内で宣言されている場合、ネストされていると見なされます。

手続き型の使用は、手続き変数だけに限定されません。 他の型と同様に、手続き型は構造型の宣言に参加できます。

プロシージャ変数にプロシージャの値が割り当てられると、物理層でプロシージャのアドレスが変数に格納されます。 実際、プロシージャ変数はポインタ変数と非常に似ていますが、データを参照する代わりに、プロシージャまたは関数を指します。 ポインタと同様に、手続き型変数はメモリ アドレスを含む 4 バイト (XNUMX ワード) を占有します。 最初のワードはオフセットを格納し、XNUMX 番目のワードはセグメントを格納します。

手続き型パラメーター

手続き型はあらゆる文脈で使用できるため、手続きや関数をパラメータとして取る手続きや関数を記述することができます。 手続き型パラメーターは、複数の手続きまたは関数に対して共通のアクションを実行する必要がある場合に特に役立ちます。

プロシージャまたは関数をパラメーターとして渡す場合は、代入と同じ型の互換性規則に従う必要があります。 つまり、そのようなプロシージャまたは関数は far ディレクティブを使用してコンパイルする必要があり、組み込み関数にすることはできず、入れ子にすることも、インラインまたは割り込み属性で記述することもできません。

LECTURE #5. 文字列データ型

1. Pascal の文字列型

一定の長さの文字列を文字列と呼びます。 文字列型の変数は、変数の名前、予約語文字列を指定し、必要に応じて最大サイズ (つまり、文字列の長さ) を角括弧で指定することによって定義されます。 最大文字列サイズを設定しない場合、デフォルトで 255 になります。つまり、文字列は 255 文字で構成されます。

文字列の各要素は、その番号で参照できます。 ただし、文字列の入力と出力は、配列の場合のように要素ごとではなく、全体として実行されます。 入力する文字数は最大文字列サイズで指定された数を超えてはならないため、このような超過が発生した場合、「余分な」文字は無視されます。

2. 文字列型変数の手続きと関数

1. Function Copy(S: 文字列; インデックス, カウント: 整数): 文字列;

文字列の部分文字列を返します。 SはString型の式です。

Index と Count は整数型の式です。 この関数は、Index 位置から始まる Count 文字を含む文字列を返します。 Index が S の長さより大きい場合、関数は空の文字列を返します。

2. Procedure Delete(var S: String; Index, Count: Integer);

長さ Count の文字の部分文字列を文字列 S から位置 Index から削除します。 S は String 型の変数です。 Index と Count は整数型の式です。 Index が S の長さより大きい場合、文字は削除されません。

3.プロシージャInsert(ソース:文字列; var S:文字列;インデックス:整数);

指定された位置から開始して、部分文字列を文字列に連結します。 SourceはString型の式です。 Sは、任意の長さのString型の変数です。 インデックスは整数型の式です。 挿入は、位置S[インデックス]から開始してソースをSに挿入します。

4.関数の長さ(S:文字列):整数;

文字列 S で実際に使用されている文字数を返します。null で終わる文字列を使用する場合、文字数は必ずしもバイト数と同じではないことに注意してください。

5. 関数 Pos(Substr: 文字列; S: 文字列): 整数;

文字列内の部分文字列を検索します。 Pos は S 内の Substr を検索し、S 内の Substr の最初の文字のインデックスである整数値を返します。Substr が見つからない場合、Pos は null を返します。

3. 録音

レコードは、さまざまなタイプに属する限られた数の論理的に関連するコンポーネントのコレクションです。 レコードのコンポーネントはフィールドと呼ばれ、各フィールドは名前で識別されます。 レコードフィールドには、フィールドの名前が含まれ、その後にフィールドのタイプを示すコロンが続きます。 レコードフィールドは、ファイルタイプを除いて、Pascalで許可されている任意のタイプにすることができます。

Pascal言語でのレコードの記述は、サービスワードRECORDを使用して実行され、その後にレコードのコンポーネントの記述が続きます。 エントリの説明は、サービスワードENDで終わります。

たとえば、ノートブックには姓、イニシャル、電話番号が含まれているため、次のエントリとしてノートブック内の別の行を表すと便利です。

タイプ行=レコード

FIO:文字列[20];

電話: 文字列[7];

終わり

var str: 行;

タイプ名を使用しないレコードの説明も可能です。たとえば、次のようになります。

var str : 記録

FIO : 文字列[20];

TEL : 文字列[7];

終わり

レコード全体の参照は、同じタイプのレコード名が割り当て記号の左右に使用されている割り当てステートメントでのみ許可されます。 他のすべての場合、レコードの個別のフィールドが操作されます。 個々のレコードコンポーネントを参照するには、レコード名を指定し、ドットを使用して目的のフィールドの名前を指定する必要があります。 このような名前は複合名と呼ばれます。 レコードコンポーネントはレコードにすることもできます。その場合、識別名にはXNUMXつではなく、より多くの名前が含まれます。

with append 演算子を使用すると、レコード コンポーネントの参照を簡略化できます。 各フィールドを特徴付ける複合名をフィールド名だけに置き換え、結合ステートメントでレコード名を定義できます。

個々のレコードの内容が、そのフィールドの XNUMX つの値に依存する場合があります。 Pascal 言語では、共通部分と異体部分から構成されるレコード記述が許可されます。 バリアント部分は、コンストラクトのケース P を使用して指定されます。ここで、P は、レコードの共通部分からのフィールドの名前です。 このフィールドで受け入れられる可能な値は、バリアント ステートメントと同じ方法でリストされます。 ただし、バリアント ステートメントで行われるように、実行するアクションを指定する代わりに、バリアント フィールドを括弧で指定します。 バリアント部分の説明はサービス語 end で終わります。 フィールド タイプ P は、バリアント パーツのヘッダーで指定できます。 レコードは、型付き定数を使用して初期化されます。

4.セット

Pascal 言語における集合の概念は、集合の数学的概念に基づいています。集合は、さまざまな要素の限定されたコレクションです。 列挙型または間隔データ型は、具体的なセット型を構築するために使用されます。 セットを構成する要素の型は、基本型と呼ばれます。

複数のタイプは、機能語のセットを使用して記述されます。次に例を示します。

タイプM=Bのセット;

ここで、M は複数型、B は基本型です。

変数が複数型に属するかどうかは、変数宣言セクションで直接決定できます。

セット型定数は、コンマで区切られた、基本型要素または間隔の括弧で囲まれたシーケンスとして記述されます。 [] の形式の定数は、空のサブセットを意味します。

セットには、基本タイプの要素のセット、指定されたセットのすべてのサブセット、および空のサブセットが含まれます。 セットが構築されている基本タイプにK個の要素がある場合、このセットに含まれるサブセットの数は2のK乗に等しくなります。基本タイプの要素を定数にリストする順序は関係ありません。 複数タイプの変数の値は、[T]の形式で構成することで指定できます。ここで、Tは基本タイプの変数です。

代入 (:=)、和集合 (+)、交差 (*)、および減算 (-) 演算は、集合型の変数と定数に適用できます。 これらの操作の結果は、複数型の値です。

1)['A'、'B'] + ['A'、'D']は['A'、'B'、'D']を与えます;

2) ['A'] * ['A','B','C'] は ['A'] を返します。

3) ['A','B','C'] - ['A','B'] は ['C'] を返します。

操作は、同一 (=)、非同一 (<>)、内包 (<=)、内包 (>=) の複数の値に適用できます。 これらの操作の結果は、ブール型になります。

1)['A'、'B'] = ['A'、'C']はFALSEを返します;

2) ['A','B'] <> ['A','C'] は TRUE を返します。

3)['B'] <= ['B'、'C']はTRUEを返します。

4) ['C','D'] >= ['A'] は FALSE を返します。

これらの操作に加えて、セットタイプの値を操作するために、操作記号の左側にある基本タイプの要素が操作記号の右側にあるセットに属しているかどうかをチェックするin操作が使用されます。 この操作の結果はブール値です。 要素がセットに属しているかどうかをチェックする操作は、リレーショナル操作の代わりによく使用されます。

プログラムで複数のデータ型を使用する場合、演算はデータのビット列に対して実行されます。 コンピューター メモリ内の倍数型の各値は、XNUMX つの XNUMX 進数に対応します。

複数のタイプの値をI/Oリストの要素にすることはできません。 Pascal言語からのコンパイラの具体的な実装ごとに、セットが構築される基本タイプの要素の数が制限されます。

複数の型の値の初期化は、型付き定数を使用して行われます。

セットを操作するためのいくつかの手順を次に示します。

1.プロシージャExclude(var S:Set of T; I:T);

セット S から要素 I を削除します。S は "set" 型の変数で、I は S の元の型と互換性のある型の式です。Exclude(S, I) は S と同じです: = S - [I] 、より効率的なコードを生成します。

2. プロシージャ Include(var S: T のセット; I:T);

セット S に要素 I を追加します。S は「セット」型の変数であり、I は型 S と互換性のある型の式です。Include(S, I) 構造は S と同じです : = S + [ I] ですが、より効率的なコードを生成します。

LECTURE No. 6. ファイル

1. ファイル。 ファイル操作

Pascal 言語にファイル タイプが導入されたのは、入力、出力、およびデータ ストレージ用に設計された周辺 (外部) コンピューター デバイスを操作する機能を提供する必要があるためです。

ファイル データ型 (またはファイル) は、同じ型の任意の数のコンポーネントの順序付きコレクションを定義します。 配列、セット、およびレコードの共通のプロパティは、それらのコンポーネントの数はプログラムを記述する段階で決定されるのに対し、プログラム テキスト内のファイル コンポーネントの数は決定されず、任意である可能性があるということです。

ファイルを操作するときは、I / O操作が実行されます。 入力操作は、外部デバイスから (入力ファイルから) コンピュータのメイン メモリにデータを転送することを意味し、出力操作は、メイン メモリから外部デバイスに (出力ファイルに) データを転送することです。 外部デバイス上のファイルは、多くの場合、物理ファイルと呼ばれます。 それらの名前は、オペレーティング システムによって決定されます。

Pascalプログラムでは、ファイル名は文字列を使用して指定されます。 プログラム内のファイルを操作するには、ファイル変数を定義する必要があります。 Pascalは、テキストファイル、コンポーネントファイル、型なしファイルのXNUMXつのファイルタイプをサポートしています。

プログラムで宣言されたファイル変数は、論理ファイルと呼ばれます。 データ I/O を提供するすべての基本的な手順と関数は、論理ファイルでのみ機能します。 ファイルを開く手順を実行する前に、物理ファイルを論理ファイルに関連付ける必要があります。

テキストファイル

Pascal言語の特別な場所は、テキストファイルで占められており、そのコンポーネントは文字タイプです。 テキストファイルを記述するために、言語は標準タイプのテキストを定義します。

var TF1、TF2:テキスト;

テキスト ファイルは一連の行であり、行は一連の文字です。 行の長さは可変で、各行は行末記号で終わります。

コンポーネント ファイル

コンポーネントまたは型付きファイルは、そのコンポーネントの型が宣言されたファイルです。 コンポーネント ファイルは、変数値のマシン表現で構成され、コンピューター メモリと同じ形式でデータを格納します。

ファイルタイプの値の説明は次のとおりです。

タイプM=ファイルオブT;

ここで、M はファイル タイプの名前です。

T - コンポーネント タイプ。

ファイルコンポーネントは、すべてスカラー型にすることができ、構造化された型(配列、セット、レコード)からのものにすることができます。 Pascal言語のほとんどすべての特定の実装では、「ファイルのファイル」構造は許可されていません。

コンポーネント ファイルに対するすべての操作は、標準的な手順を使用して実行されます。

Write(f、X1、X2、... XK)

型なしファイル

型指定されていないファイルを使用すると、コンピュータメモリの任意のセクションをディスクに書き込んだり、ディスクからメモリに読み取ったりすることができます。 型指定されていないファイルは次のように説明されます。

var f:ファイル;

ここで、さまざまなタイプのファイルを操作するための手順と関数をリストします。

1. 手順 Assign(var F; FileName: String);

AssignFileプロシージャは、外部ファイル名をファイル変数にマップします。

F は任意のファイル タイプのファイル変数、FileName は String 式、または拡張構文が許可されている場合は PChar 式です。 F を使用した以降のすべての操作は、外部ファイルを使用して実行されます。

すでに開いているファイル変数でプロシージャを使用することはできません。

2. プロシージャ Close(varF);

このプロシージャは、ファイル変数と外部ディスクファイルの間のリンクを解除し、ファイルを閉じます。

F は、Reset、Rewrite、または Append プロシージャによって開かれる任意のファイル タイプのファイル変数です。 F に関連付けられた外部ファイルは完全に変更されてから閉じられ、ファイル記述子が解放されて再利用できるようになります。

{SI+} ディレクティブを使用すると、例外処理を使用してプログラム実行中にエラーを処理できます。 {$1-} ディレクティブをオフにすると、IOResult を使用して I/O エラーをチェックする必要があります。

3.関数Eof(var F):ブール値;

{型付きまたは型なしのファイル}

関数Eof[(var F:Text)]:ブール値;

{テキストファイル}

現在のファイル位置がファイルの終わりであるかどうかを確認します。

Eof(F) は、現在のファイル位置がファイルの最後の文字の後にある場合、またはファイルが空の場合に True を返します。 それ以外の場合、Eof(F) は False を返します。

{SI+} ディレクティブを使用すると、例外処理を使用してプログラム実行中にエラーを処理できます。 {SI-} ディレクティブをオフにすると、IOResult を使用して I/O エラーをチェックする必要があります。

4. 手順 Erase(var F);

F に関連付けられた外部ファイルを削除します。

F は、任意のファイル タイプのファイル変数です。

消去プロシージャを呼び出す前に、ファイルを閉じる必要があります。

{SI+} ディレクティブを使用すると、例外処理を使用してプログラム実行中にエラーを処理できます。 {SI-} ディレクティブをオフにすると、IOResult を使用して I/O エラーをチェックする必要があります。

5. 関数 FileSize(var F): 整数;

ファイル F のサイズをバイト単位で返します。ただし、F が型付きファイルの場合、FileSize はファイル内のレコード数を返します。 FileSize 関数を使用する前に、ファイルを開いておく必要があります。 ファイルが空の場合、FileSize(F) はゼロを返します。 F は任意のファイル タイプの変数です。

6.関数FilePos(var F):LongInt;

ファイル内のファイルの現在位置を返します。

FilePos関数を使用する前に、ファイルを開いておく必要があります。 FilePos関数は、テキストファイルでは使用されません。 Fは、テキストタイプを除く、任意のファイルタイプの変数です。

7. 手順 Reset(var F [: File; RecSize: Word]);

既存のファイルを開きます。

F は、AssignFile を使用して外部ファイルに関連付けられた任意のファイル タイプの変数です。 RecSize は、F が型指定されていないファイルである場合に使用されるオプションの式です。 F が型なしファイルの場合、RecSize はデータ転送時に使用されるレコード サイズを決定します。 RecSize を省略した場合、デフォルトのレコード サイズは 128 バイトです。

Reset プロシージャは、ファイル変数 F に関連付けられた既存の外部ファイルを開きます。その名前の外部ファイルがない場合、実行時エラーが発生します。 F に関連付けられたファイルが既に開いている場合は、まず閉じてから再度開きます。 現在のファイル位置はファイルの先頭に設定されます。

8.プロシージャRewrite(var F:File [; Recsize:Word]);

新しいファイルを作成して開きます。

Fは、AssignFileを使用して外部ファイルに関連付けられた任意のファイルタイプの変数です。 RecSizeは、Fが型指定されていないファイルの場合に使用されるオプションの式です。 Fが型指定されていないファイルの場合、RecSizeは、データの転送時に使用されるレコードサイズを決定します。 RecSizeを省略した場合、デフォルトのレコードサイズは128バイトです。

Rewrite プロシージャは、F に関連付けられた名前で新しい外部ファイルを作成します。同じ名前の外部ファイルが既に存在する場合、そのファイルは削除され、新しい空のファイルが作成されます。

9.プロシージャシーク(var F; N:LongInt);

現在のファイル位置を指定されたコンポーネントに移動します。 この手順は、型付きファイルまたは型なしファイルを開いた状態でのみ使用できます。

ファイル F の現在位置は番号 N に移動されます。ファイルの最初のコンポーネントの番号は 0 です。

Seek(F、FileSize(F))命令は、現在のファイル位置をファイルの末尾に移動します。

10.プロシージャAppend(var F:Text);

既存のテキスト ファイルを開いて、ファイルの末尾に情報を追加します (append)。

指定された名前の外部ファイルが存在しない場合、実行時エラーが発生します。 ファイルFがすでに開いている場合は、ファイルFを閉じて再度開きます。 現在のファイル位置はファイルの終わりに設定されます。

11.関数Eoln[(var F:Text)]:ブール値;

現在のファイル位置がテキスト ファイルの行末かどうかを確認します。

Eoln(F)は、現在のファイル位置が行またはファイルの終わりにある場合にTrueを返します。 それ以外の場合、Eoln(F)はFalseを返します。

12. プロシージャ Read(F, V1 [, V2,..., Vn]);

{型付きファイルと型なしファイル}

プロシージャ Read([var F: Text;] V1 [, V2,..., Vn]);

{テキストファイル}

型付きファイルの場合、プロシージャはファイル コンポーネントを変数に読み込みます。 読み取りごとに、ファイル内の現在の位置が次の要素に進みます。

テキストファイルの場合、XNUMXつ以上の値がXNUMXつ以上の変数に読み込まれます。

String 変数を使用すると、Read は次の行末マーカーまで (ただし、行末マーカーは含まない)、または Eof(F) が True と評価されるまで、すべての文字を読み取ります。 結果の文字列が変数に代入されます。

整数型または実数型の変数の場合、プロシージャは Pascal 構文の規則に従って数値を形成する一連の文字を待機します。 最初のスペース、タブ、または行末が検出されるか、Eof(F) が True と評価されると、読み取りが停止します。 数値文字列が予想される形式と一致しない場合、I/O エラーが発生します。

13. プロシージャ Readln([var F: Text;] V1 [, V2..., Vn]);

これは Read プロシージャの拡張であり、テキスト ファイル用に定義されています。 行末マーカーを含むファイル内の文字列を読み取り、次の行の先頭に進みます。 パラメータを指定せずに Readln(F) 関数を呼び出すと、現在のファイル位置が次の行の先頭に移動し、存在しない場合はファイルの末尾にジャンプします。

14. 関数 SeekEof[(var F: テキスト)]: ブール値;

ファイルの終わりを返し、開いているテキスト ファイルにのみ使用できます。 通常、テキスト ファイルから数値を読み取るために使用されます。

15. 関数 SeekEoln[(var F: テキスト)]: ブール値;

ファイル内の行末記号を返します。開いているテキスト ファイルにのみ使用できます。 通常、テキスト ファイルから数値を読み取るために使用されます。

16. プロシージャ Write([var F: Text;] P1 [, P2,..., Pn]);

{テキストファイル}

XNUMX つ以上の値をテキスト ファイルに書き込みます。

各エントリパラメータは、Char型、整数型(Byte、ShortInt、Word、Longint、Cardinal)のいずれか、浮動小数点型(Single、Real、Double、Extended、Currency)のいずれか、文字列型(Single、Real、Double、Extended、Currency)のいずれかである必要があります。 PChar、AisiString、ShortString)、またはブール型のXNUMXつ(Boolean、Bool)。

プロシージャ Write(F, V1,..., Vn);

{入力されたファイル}

変数をファイルコンポーネントに書き込みます。変数 VI....、Vn はファイル要素と同じ型である必要があります。変数が書き込まれるたびに、ファイル内の現在位置が次の要素に移動します。

17. プロシージャ Writeln([var F: Text;] [P1, P2,..., Pn]);

{テキストファイル}

書き込み操作を実行し、ファイルに行末マーカーを配置します。

パラメーターを指定せずに Writeln(F) を呼び出すと、行末マーカーがファイルに書き込まれます。 ファイルは出力用に開いている必要があります。

2. モジュール。 モジュールの種類

Pascal のモジュール (1Ж1Т) は、特別に設計されたサブルーチンのライブラリです。 モジュールは、プログラムとは異なり、単独で実行するために起動することはできず、プログラムや他のモジュールの構築にのみ参加できます。 モジュールを使用すると、プロシージャと関数の個人用ライブラリを作成し、ほぼすべてのサイズのプログラムを構築できます。

Pascal のモジュールは、個別に保存され、個別にコンパイルされたプログラム単位です。 一般に、モジュールは、他のプログラムで使用するためのソフトウェア リソースの集まりです。 プログラム リソースは、Pascal 言語の任意の要素 (定数、型、変数、サブルーチン) として理解されます。 モジュール自体は実行可能なプログラムではなく、その要素は他のプログラムユニットによって使用されます。

モジュールのすべてのプログラム要素は、次のXNUMXつの部分に分けることができます。

1)他のプログラムまたはモジュールによる使用を目的としたプログラム要素。このような要素は、モジュールの外部で表示可能と呼ばれます。

2)モジュール自体の操作にのみ必要なソフトウェア要素は、非表示(または非表示)と呼ばれます。

これに従って、ヘッダーに加えて、モジュールには、インターフェース、実行可能、および初期化と呼ばれる XNUMX つの主要部分が含まれます。

一般に、モジュールには次の構造があります。

unit <モジュール名>; {モジュールのタイトル}

インタフェース

{モジュールの目に見えるプログラム要素の説明}

実装

{モジュールの隠されたプログラミング要素の説明}

始まる

{モジュール要素の初期化ステートメント}

終わり。

特定のケースでは、モジュールに実装部分と初期化部分が含まれていない場合、モジュール構造は次のようになります。

unit <モジュール名>; {モジュールのタイトル}

インタフェース

{モジュールの目に見えるプログラム要素の説明}

実装

終わり。

モジュールでのプロシージャと関数の使用には、独自の特徴があります。 サブルーチン ヘッダーには、それを呼び出すために必要なすべての情報 (名前、パラメーターのリストと型、関数の結果の型) が含まれています。 この情報は、他のプログラムやモジュールで利用できる必要があります。 一方、そのアルゴリズムを実装するサブルーチンのテキストは、他のプログラムやモジュールで使用できません。 したがって、プロシージャと関数の見出しはモジュールのインターフェイス部分に配置され、テキストは実装部分に配置されます。

モジュールのインターフェイス部分には、プロシージャと関数の可視 (他のプログラムやモジュールからアクセス可能) なヘッダーのみが含まれます (サービス ワード forward は含まれません)。 プロシージャまたは関数の全文は実装部分に配置され、ヘッダーには仮パラメータのリストが含まれていない場合があります。

モジュールのソース コードは、Compile サブメニューの Make ディレクティブを使用してコンパイルし、ディスクに書き込む必要があります。 モジュールのコンパイル結果は、拡張子 . TPU(ターボパスカルユニット)。 モジュールのベース名は、モジュールのヘッダーから取得されます。

モジュールをプログラムに接続するには、モジュールの説明セクションでその名前を指定する必要があります。次に例を示します。

Crt、Graphを使用します。

モジュールのインタフェース部分と、このモジュールを使用したプログラムの変数名が同じ場合は、プログラムに記述された変数が参照されます。 モジュールで宣言された変数を参照するには、モジュール名と変数名をドットで区切った複合名を使用する必要があります。 複合名の使用は、変数名だけでなく、モジュールのインターフェース部分で宣言されたすべての名前にも適用されます。

モジュールの再帰的な使用は禁止されています。

モジュールに初期化セクションがある場合、そのモジュールを使用するプログラムが実行を開始する前に、そのセクションのステートメントが実行されます。

モジュールの種類を挙げてみましょう。

1. システムモジュール。

SYSTEMモジュールは、I / O、文字列操作、浮動小数点演算、動的メモリ割り当てなど、すべての組み込み機能に対して低レベルのサポートルーチンを実装します。

SYSTEM モジュールには、すべての標準および組み込みの Pascal ルーチンと関数が含まれています。 標準の Pascal の一部ではなく、他のどのモジュールにもない Pascal サブルーチンは、System モジュールに含まれています。 このユニットはすべてのプログラムで自動的に使用され、uses ステートメントで指定する必要はありません。

2.DOS モジュール。

Dos モジュールは、GetTime、SetTime、DiskSize など、最も一般的に使用される DOS 呼び出しと同等の多数の Pascal ルーチンと関数を実装しています。

3. CRT モジュール。

CRTモジュールは、画面モード制御、拡張キーボードコード、色、ウィンドウ、サウンドなど、PCの機能を完全に制御する多数の強力なプログラムを実装しています。 CRTモジュールは、IBMのパーソナルコンピューターIBM PC、PC AT、PS / 2で実行され、それらと完全に互換性のあるプログラムでのみ使用できます。

CRT モジュールを使用する主な利点の XNUMX つは、画面操作を実行する際の速度と柔軟性の向上です。 CRT モジュールで動作しないプログラムは、DOS オペレーティング システムを使用して画面に情報を表示しますが、これには追加のオーバーヘッドが伴います。 CRT モジュールを使用する場合、出力情報は基本入出力システム (BIOS) に直接送信されるか、さらに高速な操作のためにビデオ メモリに直接送信されます。

4.GRAPHモジュール。

このモジュールに含まれている手順と機能を使用して、画面上にさまざまなグラフィックを作成できます。

5. オーバーレイ モジュール。

OVERLAYモジュールを使用すると、リアルモードDOSプログラムのメモリ要件を減らすことができます。 実際、一度にメモリに格納されるのはプログラムの一部のみであるため、使用可能なメモリの合計量を超えるプログラムを作成することは可能です。

LECTURE № 7. 動的記憶

1.参照データ型。 動的メモリ。 動的変数

静的変数 (静的に割り当てられた) は、プログラムで明示的に宣言された変数であり、名前で参照されます。 静的変数を配置するメモリ内の場所は、プログラムのコンパイル時に決定されます。 このような静的変数とは異なり、Pascal プログラムでは動的変数を作成できます。 動的変数の主な特性は、それらが作成され、プログラムの実行中にメモリが割り当てられることです。

動的変数は、動的メモリ領域 (ヒープ領域) に配置されます。 動的変数は、変数宣言で明示的に指定されていないため、名前で参照できません。 このような変数には、ポインターと参照を使用してアクセスします。

参照型 (ポインター) は、基本型と呼ばれる特定の型の動的変数を指す一連の値を定義します。 参照型変数には、メモリ内の動的変数のアドレスが含まれます。 基本型が宣言されていない識別子である場合は、型宣言の同じ部分でポインター型として宣言する必要があります。

予約語 nil は、ポインタ値が何も指していない定数を示します。

動的変数の記述例を挙げましょう。

var p1、p2:^ real;

p3、p4 : ^整数;

2.動的メモリの操作。 型指定されていないポインタ

動的メモリの手続きと機能

1. プロシージャ New(var p: ポインタ)。

動的変数 p を収容するために、動的メモリ領域にスペースを割り当てます。Л、およびそのアドレスをポインタpに割り当てます。

2. Dispose(varp: Pointer) 手続き。

Newプロシージャによって動的変数割り当てに割り当てられたメモリを解放し、ポインタpの値が未定義になります。

3. 手順 GetMem(varp: ポインタ; サイズ: ワード)。

ヒープ領域にメモリセクションを割り当て、その先頭のアドレスをポインタpに割り当てます。セクションのサイズ(バイト単位)は、sizeパラメータで指定されます。

4.プロシージャFreeMem(var p:ポインタ;サイズ:Word)。

pポインタで指定された先頭アドレス、sizeパラメータで指定されたサイズのメモリ領域を解放します。 ポインタ p の値は不定になります。

5. 手続きマーク(var p: ポインタ)

呼び出し時に、空き動的メモリのセクションの先頭のアドレスをポインタ p に書き込みます。

6.プロシージャリリース(var p:ポインタ)

Markプロシージャによってポインタpに書き込まれたアドレスから開始して、動的メモリのセクションを解放します。つまり、Markプロシージャの呼び出し後に占有されていた動的メモリをクリアします。

7. MaxAvaikLongint 関数

最長の空きヒープの長さをバイト単位で返します。

8.MemAvaikLongint関数

空き動的メモリの合計量をバイト単位で返します。

9.ヘルパー関数SizeOf(X):Word

X が占めるバイト数を返します。X は、任意の型の変数名または型名のいずれかです。

組み込み型 Pointer は、型指定されていないポインター、つまり、特定の型を指していないポインターを示します。 Pointer 型の変数は逆参照できます。そのような変数の後に ^ 文字を指定すると、エラーが発生します。

nil で示される値と同様に、ポインター値は他のすべてのポインター型と互換性があります。

LECTURE № 8. 抽象的なデータ構造

1.抽象的なデータ構造

配列、セット、レコードなどの構造化データ型は、プログラムの実行中にサイズが変わらないため、静的構造です。

多くの場合、問題を解決する過程でデータ構造のサイズを変更する必要があります。 このようなデータ構造は動的と呼ばれます。 これらには、スタック、キュー、リスト、ツリーなどが含まれます。

配列、レコード、およびファイルを使用して動的構造を記述すると、コンピューターのメモリが無駄に使用され、問題を解決するための時間が長くなります。

動的構造の各コンポーネントは、少なくともXNUMXつのフィールドを含むレコードです。XNUMXつは「ポインタ」タイプのフィールドで、もうXNUMXつはデータ配置用です。 一般に、レコードにはXNUMXつではなく、複数のポインターと複数のデータフィールドが含まれる場合があります。 データフィールドには、変数、配列、セット、またはレコードを指定できます。

ポインティング部分にリストの XNUMX つの要素のアドレスが含まれている場合、リストは単方向 (または単一リンク) と呼ばれます。 XNUMX つのコンポーネントが含まれている場合は、二重接続されています。 リストに対してさまざまな操作を実行できます。次に例を示します。

1) リストに要素を追加する。

2) 指定されたキーを持つリストから要素を削除します。

3)キーフィールドの指定された値を持つ要素を検索します。

4)リストの要素を並べ替えます。

5)リストをXNUMXつ以上のリストに分割する。

6)XNUMXつ以上のリストをXNUMXつに結合する。

7) その他の操作。

ただし、原則として、さまざまな問題を解決するためにすべての操作が必要になるわけではありません。 したがって、適用する必要がある基本的な操作に応じて、さまざまな種類のリストがあります。 これらの中で最も一般的なのはスタックとキューです。

2.スタック

スタックは動的なデータ構造であり、コンポーネントの追加とコンポーネントの削除は、スタックの最上位と呼ばれる一方の端から行われます。 スタックは、LIFO (Last-In, First-Out) - 「後入れ先出し」の原則に基づいて機能します。

通常、スタックに対して実行される操作は XNUMX つあります。

1) スタックの初期形成 (最初のコンポーネントの記録);

2) コンポーネントをスタックに追加する。

3) コンポーネントの選択 (削除)。

スタックを形成して操作するには、「ポインター」型の XNUMX つの変数が必要です。XNUMX つ目はスタックのトップを決定し、XNUMX つ目は補助変数です。

例。 スタックを形成し、それに任意の数のコンポーネントを追加してから、すべてのコンポーネントを読み取り、それらを表示画面に表示するプログラムを作成します。 文字列をデータとして取ります。 データ入力-キーボードから、入力の終わりの記号-文字列END。

プログラム STACK;

Crt を使用します。

type

アルファ = 文字列[10];

PComp = ^Comp;

Comp=レコード

SD : アルファ

pNext:PComp

終わり

VAR

pTop:PComp;

sc: アルファ;

ProcedureStack を作成します(var pTop : PComp; var sC : Alfa);

始まる

新しい (pTop);

pTop^.pNext := NIL;

pTop^.sD := sC;

終わり

ProcedureComp(var pTop:PComp; var sC:Alfa);を追加します。

var pAux:PComp;

始まる

新しい(pAux);

pAux^.pNext := pTop;

pTop:= pAux;

pTop^.sD := sC;

終わり

手順 DelComp(var pTop : PComp; var sC : ALFA);

始まる

sC := pTop^.sD;

pTop := pTop^.pNext;

終わり

始まる

Clrscr;

writeln(' ENTER STRING');

readln(SC);

CreateStack(pTop、sc);

繰り返す

writeln(' ENTER STRING');

readln(SC);

AddComp(pTop、sc);

sC = 'END' まで;

writeln('****** OUTPUT ******');

繰り返す

DelComp(pTop、sc);

writeln(sC);

pTop = NILまで;

終わり。

3. 待ち行列

キューは動的なデータ構造であり、一方の端でコンポーネントが追加され、もう一方の端で取得されます。 キューは、FIFO (First-In, First-Out) - 「先入れ先出し」の原則に基づいて機能します。

キューを作成して操作するには、ポインター型の XNUMX つの変数が必要です。最初の変数はキューの開始を決定し、XNUMX 番目はキューの終了を決定し、XNUMX 番目は補助変数です。

例。 キューを形成し、それに任意の数のコンポーネントを追加してから、すべてのコンポーネントを読み取り、それらを表示画面に表示するプログラムを作成します。 文字列をデータとして取ります。 データ入力-キーボードから、入力の終わりの記号-文字列END。

ProgramQUEUE;

Crt を使用します。

type

アルファ = 文字列[10];

PComp = ^Comp;

コンプ=記録

SD : アルファ

pNext : PComp;

終わり

VAR

pBegin、pEnd:PComp;

sc: アルファ;

ProcedureQueue(var pBegin、pEnd:PComp; var sC:Alfa);を作成します。

始まる

新しい (pBegin);

pBegin^.pNext := NIL;

pBegin^.sD := sC;

pEnd:= pBegin;

終わり

Procedure Add ProcedureQueue(var pEnd : PComp; var sC : Alfa);

var pAux:PComp;

始まる

New(pAux);

pAux ^ .pNext:= NIL;

pEnd^.pNext := pAux;

pEnd:= pAux;

pEnd^.sD := sC;

終わり

手順 DelQueue(var pBegin : PComp; var sC : Alfa);

始まる

sC := pBegin^.sD;

pBegin:= pBegin^.pNext;

終わり

始まる

Clrscr;

writeln(' ENTER STRING');

readln(SC);

CreateQueue(pBegin、pEnd、sc);

繰り返す

writeln(' ENTER STRING');

readln(SC);

AddQueue(pEnd, sc);

sC = 'END' まで;

writeln(' ***** 結果の表示 *****');

繰り返す

DelQueue(pBegin、sc);

writeln(sC);

pBegin=NILまで;

終わり。

LECTURE No. 9. ツリー状のデータ構造

1. ツリーデータ構造

ツリー状のデータ構造は、要素とノードの有限集合であり、その間に関係 (ソースと生成されたものとの間の接続) があります。

N. Wirth によって提案された再帰的な定義を使用する場合、基本型 t を持つツリー データ構造は、空の構造体または型 t のノードのいずれかであり、サブツリーと呼ばれる基本型 t を持つツリー構造の有限集合は、関連する。

次に、ツリー構造を操作するときに使用される定義を示します。

ノード y がノード x の直下に位置する場合、ノード y はノード x の直接の子孫と呼ばれ、x はノード y の直接の祖先となります。つまり、ノード x が i 番目のレベルにある場合、ノード y はそれに応じて(i + 1) 番目のレベルにあります。

ツリー ノードの最大レベルは、ツリーの高さまたは深さと呼ばれます。 祖先には、ツリーのノード (ルート) が XNUMX つしかないわけではありません。

子を持たないツリー ノードは、リーフ ノード (またはツリーの葉) と呼ばれます。 他のすべてのノードは内部ノードと呼ばれます。 ノードの直接の子の数によってそのノードの次数が決まり、特定のツリー内のノードの最大次数によってツリーの次数が決まります。

祖先と子孫を交換することはできません。つまり、元の動作と生成された動作の間の接続は一方向にのみ作用します。

ツリーのルートから特定のノードに移動する場合、この場合にトラバースされるツリーのブランチの数は、このノードのパスの長さと呼ばれます。 ツリーのすべてのブランチ (ノード) が順序付けられている場合、そのツリーは順序付けられていると言われます。

二分木は、木構造の特殊なケースです。 これらは、各子が最大 XNUMX つの子を持つツリーであり、左サブツリーと右サブツリーと呼ばれます。 したがって、二分木は次数が XNUMX のツリー構造です。

バイナリツリーの順序は、次のルールによって決定されます。各ノードには独自のキーフィールドがあり、各ノードのキー値は、左側のサブツリーのすべてのキーよりも大きく、右側のサブツリーのすべてのキーよりも小さくなります。

次数がXNUMXより大きいツリーは、強分岐と呼ばれます。

2.木の操作

さらに、二分木に関連するすべての操作を検討します。

I.木の建設

順序付けられたツリーを構築するためのアルゴリズムを提示します。

1.ツリーが空の場合、データはツリーのルートに転送されます。 ツリーが空でない場合、ツリーの順序に違反しないように、そのブランチのXNUMXつが下降します。 その結果、新しいノードがツリーの次のリーフになります。

2. 既存のツリーにノードを追加するには、上記のアルゴリズムを使用できます。

3.ツリーからノードを削除するときは、注意が必要です。 削除するノードがリーフの場合、または子がXNUMXつしかない場合、操作は簡単です。 削除するノードにXNUMXつの子孫がある場合、その子孫の中からその場所に配置できるノードを見つける必要があります。 これは、ツリーを注文する必要があるために必要です。

これを行うことができます:削除するノードを、左側のサブツリーで最大のキー値を持つノード、または右側のサブツリーで最小のキー値を持つノードと交換してから、目的のノードをリーフとして削除します。

II。 指定されたキーフィールド値を持つノードを検索する

この操作を実行するときは、ツリーをトラバースする必要があります。 ツリーを作成するさまざまな形式(接頭辞、中置辞、接尾辞)を考慮する必要があります。

疑問が生じます: ツリーのノードをどのように表現すれば、それらを操作するのが最も便利になるでしょうか? 配列を使用してツリーを表すことができます。各ノードは、文字型情報フィールドと XNUMX つの参照型フィールドを持つ結合型値によって記述されます。 しかし、ツリーには事前に定義されていないノードが多数あるため、これはあまり便利ではありません。 したがって、ツリーを記述するときは動的変数を使用するのが最善です。 次に、各ノードは、指定された数の情報フィールドの説明を含む同じタイプの値で表され、対応するフィールドの数はツリーの次数に等しくなければなりません。 子孫の不在を nil で定義するのは論理的です。 次に、Pascal では、バイナリ ツリーの記述は次のようになります。

TYPE TreeLink = ^ Tree;

ツリー = レコード;

Inf:<データ型>;

左、右: TreeLink;

終了

3. 運用の実施例

1. 最小の高さの n ノードのツリー、または完全にバランスの取れたツリーを構築します (このようなツリーの左右のサブツリーのノード数は XNUMX を超えてはなりません)。

再帰的構築アルゴリズム:

1) 最初のノードがツリーのルートと見なされます。

2) nl ノードの左側のサブツリーが同じ方法で構築されます。

3)nrノードの右側のサブツリーも同じ方法で作成されます。

nr = n - nl - 1. 情報フィールドとして、キーボードから入力されたノード番号を取得します。 この構造を実装する再帰関数は次のようになります。

関数ツリー(n:バイト):TreeLink;

変数:TreeLink; nl、nr、x:バイト;

始める

n = 0 の場合、ツリー := nil

始める

nl := n div 2;

nr = n - nl - 1;

writeln('頂点番号を入力');

readln(x);

new(t);

t ^ .inf:= x;

t^.left := ツリー(nl);

t^.right:= ツリー(nr);

ツリー:= t;

終わり;

{木}

終了

2. 二分順序ツリーで、指定されたキー フィールドの値を持つノードを見つけます。 ツリーにそのような要素がない場合は、ツリーに追加します。

検索手順(x : Byte; var t : TreeLink);

始める

t = nilの場合、

始める

新しい(t);

t ^ inf:= x;

t^.left := nil;

t^.right := nil;

終わり

そうでなければ x < t^.inf の場合

検索(x, t^.左)

それ以外の場合、x> t^.infの場合

Search(x、t ^ .right)

始める

{見つかった要素を処理する}

...

終わり;

終了

3.ツリー走査手順を、それぞれ順方向、対称、逆順で記述します。

3.1。 プロシージャの事前注文(t:TreeLink);

始める

t <> nil の場合

始める

WriteIn(t ^ .inf);

事前注文(t ^ .left);

事前注文(t ^ .right);

終わり;

終わり;

3.2. プロシージャ Inorder(t : TreeLink);

始める

t <> nil の場合

始める

Inorder(t^.left);

WriteIn(t ^ .inf);

Inorder(t^.right);

終わり;

終了

3.3. 手続き Postorder(t : TreeLink);

始める

t <> nil の場合

始める

postorder(t ^ .left);

postorder(t^.right);

WriteIn(t ^ .inf);

終わり;

終了

4. 二分順序ツリーで、キー フィールドの指定された値を持つノードを削除します。

ツリー内の必要な要素の存在とこのノードの子孫の数を考慮に入れる再帰的な手順を説明しましょう。 削除するノードに XNUMX つの子がある場合、そのノードはその左側のサブツリーで最大のキー値に置き換えられ、その場合にのみ完全に削除されます。

プロシージャDelete1(x:バイト; var t:TreeLink);

変数 p : ツリーリンク;

プロシージャ Delete2(var q : TreeLink);

始める

q ^ .right <> nilの場合、Delete2(q ^ .right)

始める

p ^ .inf:= q ^ .inf;

p := q;

q := q^.left;

終わり;

終わり;

始める

t = nilの場合、

Writeln('要素が見つかりません')

そうでなければ x < t^.inf の場合

Delete1(x、t ^ .left)

それ以外の場合、x> t^.infの場合

削除1(x, t^.right)

始める

P := t;

p ^ .left = nilの場合、

t:= p ^ .right

p ^ .right = nilの場合、

t := p^.left

Delete2(p ^ .left);

終わり;

終了

LECTURE No. 10. カウント

1. グラフの概念。 グラフを表現する方法

グラフはペア G = (V,E) です。ここで、V は頂点と呼ばれる任意の性質のオブジェクトのセットであり、E はエッジと呼ばれるペア ei = (vil, vi2), vijOV のファミリです。 一般に、集合 V および/または族 E は無限の数の要素を含む可能性がありますが、有限グラフ、つまり V と E の両方が有限であるグラフのみを検討します。 ei に含まれる要素の順序が重要である場合、グラフは有向グラフと呼ばれ、digraph と省略されます。それ以外の場合は、無向グラフと呼ばれます。 有向グラフのエッジはアークと呼ばれます。 以下では、特定 (有向または無向) なしで使用される「グラフ」という用語は、無向グラフを表すと仮定します。

e =の場合の場合、頂点 v と u はエッジの端点と呼ばれます。 ここで、エッジ e は頂点 v と u のそれぞれに隣接 (入射) していると言えます。 頂点 v と と は、隣接 (インシデント) とも呼ばれます。 一般的な場合、フォーム e = のエッジ; このようなエッジはループと呼ばれます。

グラフ内の頂点の次数は、その頂点に入射するエッジの数であり、ループは 1 回カウントされます。各エッジは 2 つの頂点に付随するため、グラフ内のすべての頂点の次数の合計はエッジ数の XNUMX 倍に等しくなります。 Sum(deg(vi), i=XNUMX...|V|) = XNUMX * | E|。

ノードの重みは、特定のノードに割り当てられた数値 (実数、整数、または有理数) です (コスト、スループットなどとして解釈されます)。 重み、辺の長さ - 長さ、帯域幅などとして解釈される XNUMX つまたは複数の数。

グラフ内のパス (または有向グラフ内のルート) は、v0、(v0,v1)、v1...、(vn - 1,vn) の形式の頂点とエッジ (または有向グラフ内の円弧) の交互のシーケンスです。 )、vn.数値 n は経路長と呼ばれます。エッジの繰り返しがないパスはチェーンと呼ばれ、頂点の繰り返しがないパスは単純チェーンと呼ばれます。パスは閉じることができます (v0 = vn)。エッジが繰り返されない閉じたパスは、サイクル (または有向グラフの輪郭) と呼ばれます。頂点の繰り返しなし (最初と最後の頂点を除く) - 単純なループ。

グラフは、その頂点のいずれかXNUMXつの間にパスがある場合は接続済みと呼ばれ、それ以外の場合は切断されます。 切断されたグラフは、いくつかの接続されたコンポーネント(接続されたサブグラフ)で構成されます。

グラフの表現にはさまざまな方法があります。 それぞれを別々に考えてみましょう。

1. 発生率マトリックス。

これは次元 nx n の長方形行列です。ここで、n は頂点の数、am はエッジの数です。行列要素の値は次のように決定されます。エッジ xi と頂点 vj が一致する場合、対応する行列要素の値は 1 に等しく、そうでない場合は値は 1 になります。有向グラフの場合、出現行列は次の原則に従って構築されます。要素の値は、エッジ xi が頂点 vj から来る場合は - XNUMX に等しく、エッジ xi が頂点 vj に入る場合は XNUMX に等しく、それ以外の場合は XNUMX に等しくなります。 。

2. 隣接行列。

これは次元 nxn の正方行列です。n は頂点の数です。頂点 vi と vj が隣接している場合、つまり頂点 vi と vj を接続するエッジがある場合、対応する行列要素は 1 に等しく、そうでない場合は 0 に等しくなります。この行列を構築するための規則は、有向グラフと無向グラフで変わりません。隣接行列は、出現行列よりもコンパクトです。この行列も非常に疎であることに注意してください。ただし、無向グラフの場合は主対角に関して対称であるため、行列全体ではなく半分だけを保存できます(三角行列) )。

3. 隣接関係 (インシデント) のリスト。

グラフの頂点ごとに隣接する頂点のリストを格納するデータ構造です。 リストはポインターの配列であり、その i 番目の要素には、i 番目の頂点に隣接する頂点のリストへのポインターが含まれます。

隣接リストは、null 要素の格納を排除するため、隣接行列よりも効率的です。

4.リストのリスト。

これはツリーのようなデータ構造であり、XNUMXつのブランチには各グラフの頂点に隣接する頂点のリストが含まれ、XNUMX番目のブランチは次のグラフの頂点を指します。 グラフを表すこの方法が最適です。

2.インシデントリストによるグラフの表現。 グラフの深さトラバーサルアルゴリズム

グラフを発生率リストとして実装するには、次の型を使用できます。

TypeList = ^S;

S=レコード;

inf : バイト;

次: リスト;

終わり

次に、グラフは次のように定義されます。

Var Gr : リストの配列[1..n];

それでは、グラフトラバーサル手順に移りましょう。 これは、グラフのすべての頂点を表示し、すべての情報フィールドを分析できる補助アルゴリズムです。 グラフのトラバーサルを詳細に検討すると、再帰的アルゴリズムと非再帰的アルゴリズムの XNUMX 種類があります。

再帰的な深さ優先トラバーサル アルゴリズムを使用して、任意の頂点を取得し、それに隣接する任意の見えない (新しい) 頂点 v を見つけます。 次に、頂点 v を新しくないものとして取り、それに隣接する新しい頂点を見つけます。 一部の頂点に新しい見えない頂点がない場合、この頂点が使用されていると見なし、使用済みの頂点に到達した頂点の XNUMX レベル上に戻ります。 走査は、グラフ内に新しいスキャンされていない頂点がなくなるまで、この方法で続行されます。

Pascal では、深さ優先トラバーサル手順は次のようになります。

手順 Obhod(gr : グラフ; k : バイト);

Var g:グラフ; l:リスト;

始める

nov[k]:=偽;

g := gr;

g^.inf <> k している間

g:= g ^ .next;

l := g^.smeg;

l <> nil が始まる間

nov[l^.inf] の場合、Obod(gr, l^.inf);

l := l^.next;

終わり;

終わり;

注意

この手順では、グラフタイプを説明するとき、リストのリストによるグラフの説明を意味しました。 配列nov[i]は、i番目の頂点にアクセスしない場合はi番目の要素がTrueであり、それ以外の場合はFalseである特別な配列です。

非再帰的トラバーサルアルゴリズムもよく使用されます。 この場合、再帰はスタックに置き換えられます。 頂点が表示されると、スタックにプッシュされ、隣接する新しい頂点がなくなると使用されます。

3.リストのリストによるグラフの表現。 幅グラフ走査アルゴリズム

グラフは、次のようにリストのリストを使用して定義できます。

TypeList = ^ Tlist;

tlist = record

inf : バイト;

次: リスト;

終わり

グラフ = ^TGpaph;

TGpaph = レコード

inf : バイト;

smeg : リスト;

次: グラフ;

終わり

グラフを横方向に走査するとき、任意の頂点を選択し、それに隣接するすべての頂点を一度に調べます。 スタックの代わりにキューが使用されます。 幅優先探索アルゴリズムは、グラフ内の最短経路を見つけるのに非常に便利です。

以下は、疑似コードで幅方向にグラフをトラバースする手順です。

手順 Obhod2(v);

{値spisok、nov - グローバル}

始める

キュー = O;

キュー <= v;

nov [v] = False;

キューの間<> O do

始める

p <= キュー;

spisok(p)のuについては、

新しい[u]の場合

始める

新星[u] := 偽;

キュー <= u;

終わり;

終わり;

終わり;

LECTURE #11. オブジェクトのデータ型

1. Pascal でのオブジェクト型。 オブジェクトの概念、その説明と使用

歴史的に、プログラミングへの最初のアプローチは、ボトムアップ プログラミングとも呼ばれる手続き型プログラミングでした。 当初、コンピュータアプリケーションのさまざまな分野で使用される標準プログラムの共通ライブラリが作成されました。 次に、これらのプログラムに基づいて、特定の問題を解決するためのより複雑なプログラムが作成されました。

しかし、コンピュータ技術は絶えず発展しており、生産や経済のさまざまな問題を解決するために使用されるようになったため、さまざまな形式のデータを処理し、非標準の問題(たとえば、非数値の問題)を解決する必要が生じました。 そのため、プログラミング言語を開発する際には、さまざまな種類のデータの作成に注意を払うようになりました。 これは、結合、複数、文字列、ファイルなどの複雑なデータ型の出現に貢献しました。問題を解決する前に、プログラマーは分解を実行しました。つまり、タスクをいくつかのサブタスクに分割し、それぞれに個別のモジュールを作成しました。 . 主なプログラミング技術には、次の XNUMX つの段階が含まれていました。

1) トップダウン設計;

2) モジュール式プログラミング。

3) 構造コーディング。

しかし、60 世紀の XNUMX 年代半ばから、オブジェクト指向プログラミングの技術の基礎を形成する新しい概念とアプローチが形成され始めました。 このアプローチでは、現実世界のモデル化と記述が、解決されている問題が属する特定の主題領域の概念レベルで実行されます。

オブジェクト指向プログラミングは、私たちの行動によく似たプログラミング手法です。 これは、プログラミング言語設計における初期の革新の自然な進化です。 オブジェクト指向プログラミングは、構造化プログラミングに関するこれまでのすべての開発よりも構造的です。 また、内部でのデータの抽象化とプログラミングの詳細に関する以前の試みよりもモジュール化され、抽象化されています。 オブジェクト指向プログラミング言語は、次のXNUMXつの主要な特性によって特徴付けられます。

1) カプセル化。 レコードを、これらのレコードのフィールドを操作するプロシージャおよび関数と組み合わせると、新しいデータ型 (オブジェクト) が形成されます。

2)継承。 オブジェクトの定義と、階層に関連する各子オブジェクトがすべての親オブジェクトのコードとデータにアクセスする機能を備えた子オブジェクトの階層を構築するためのさらなる使用。

3) ポリモーフィズム。 アクションに単一の名前を付けて、オブジェクトの階層を上下に共有し、階層内の各オブジェクトがそのアクションを適切な方法で実行します。

オブジェクトといえば、新しいデータ型オブジェクトを導入します。 オブジェクト型は、一定数のコンポーネントで構成される構造です。 各コンポーネントは、厳密に定義された型のデータを含むフィールド、またはオブジェクトに対して操作を実行するメソッドのいずれかです。 変数の宣言と同様に、フィールドの宣言は、このフィールドのデータ型とフィールドに名前を付ける識別子を指定します。プロシージャまたは関数の宣言との類似により、メソッドの宣言はプロシージャのタイトルを指定します。関数、コンストラクタ、またはデストラクタ。

オブジェクト タイプは、別のオブジェクト タイプのコンポーネントを継承できます。 型 T2 が型 T1 から継承する場合、型 T2 は型 T1 の子であり、型 T1 自体は型 T2 の親です。 継承は推移的です。つまり、TK が T2 から継承し、T2 が T1 から継承する場合、TK は T1 から継承します。 オブジェクト型のスコープ (ドメイン) は、それ自体とそのすべての子孫で構成されます。

次のソース コードは、オブジェクト型宣言の例です。

type

ポイント = オブジェクト

X、Y: 整数。

終わり

Rect=オブジェクト

A、B: T ポイント。

procedure Init(XA, YA, XB, YB: 整数);

プロシージャCopy(var R:TRectangle);

プロシージャ Move(DX, DY: 整数);

プロシージャGrow(DX、DY:整数);

プロシージャ Intersect(var R: TRectangle);

手順 Union(var R: TRectangle);

function Contains(P: ポイント): Boolean;

終わり

StringPtr = ^ String;

FieldPtr = ^ TField;

TField=オブジェクト

X、Y、長さ: 整数。

名前: StringPtr;

コンストラクタ Copy(var F: TField);

コンストラクターInit(FX、FY、FLen:整数; FName:文字列);

デストラクタ 完了; バーチャル;

手順 ディスプレイ; バーチャル;

手順編集; バーチャル;

関数 GetStr: 文字列; バーチャル;

関数 PutStr(S: 文字列): ブール値; バーチャル;

終わり

StrFieldPtr = ^TStField;

StrField = オブジェクト(TField)

値: PString;

コンストラクターInit(FX、FY、FLen:整数; FName:文字列);

デストラクタ 完了; バーチャル;

関数 GetStr: 文字列; バーチャル;

関数 PutStr(S: 文字列): ブール値;

バーチャル;

関数Get:string;

プロシージャPut(S:文字列);

終わり

NumFieldPtr = ^TNumField;

TNumField = オブジェクト(TField)

プライベート

値、最小、最大: 倍長整数。

公共

コンストラクター Init(FX、FY、FLen: 整数; FName: 文字列;

FMin、FMax:Longint);

関数 GetStr: 文字列; バーチャル;

関数 PutStr(S: 文字列): ブール値; バーチャル;

関数 Get: 倍長整数;

function Put(N: 倍長整数);

終わり

ZipFieldPtr = ^TZipField;

ZipField = オブジェクト(TNumField)

関数 GetStr: 文字列; バーチャル;

関数 PutStr(S: 文字列): ブール値;

バーチャル;

終わり。

他の型とは異なり、オブジェクト型は、プログラムまたはモジュールのスコープの最も外側のレベルにある型宣言セクションでのみ宣言できます。 したがって、変数宣言セクション、またはプロシージャ、関数、またはメソッド ブロック内でオブジェクト型を宣言することはできません。

ファイル タイプ コンポーネント タイプは、オブジェクト タイプまたはオブジェクト タイプ コンポーネントを含む構造タイプを持つことはできません。

2.継承

あるタイプが別のタイプの特性を継承するプロセスは、継承と呼ばれます。 子孫は派生 (子) 型と呼ばれ、子型が継承する型は親 (親) 型と呼ばれます。

以前から知られている Pascal レコード タイプは継承できません。 ただし、Borland Pascal は Pascal 言語を拡張して継承をサポートします。 これらの拡張機能の XNUMX つは、レコードに関連する新しいデータ構造カテゴリですが、より強力です。 この新しいカテゴリのデータ型は、新しい予約語「オブジェクト」を使用して定義されます。 オブジェクト型は、Pascal エントリを記述する方法で完全な独立した型として定義できますが、予約語「オブジェクト」の後に親型を括弧で囲むことにより、既存のオブジェクト型の子孫として定義することもできます。

3.オブジェクトのインスタンス化

オブジェクトのインスタンスは、オブジェクト型の変数または定数を宣言するか、標準の New プロシージャを「オブジェクト型へのポインタ」型の変数に適用することによって作成されます。 結果のオブジェクトは、オブジェクト タイプのインスタンスと呼ばれます。

VAR

F: Tフィールド;

Z:TZipField;

FP: Pフィールド;

ZP: PZipフィールド;

これらの変数宣言がある場合、FはTFieldのインスタンスであり、ZはTZipFieldのインスタンスです。 同様に、FPとZPにNewを適用した後、FPはTFieldインスタンスを指し、ZPはTZipFieldインスタンスを指します。

オブジェクトタイプに仮想メソッドが含まれている場合、そのオブジェクトタイプのインスタンスは、仮想メソッドを呼び出す前にコンストラクターを呼び出して初期化する必要があります。

以下に例を示します。

VAR

S:StrField;

エギン

S.Init(1、1、25、'名');

S.Put('ウラジミール');

S.ディスプレイ;

...

S 完了。

終わり。

S.Init が呼び出されなかった場合、S.Display を呼び出すと、この例は失敗します。

オブジェクトタイプのインスタンスを割り当てることは、インスタンスの初期化を意味するものではありません。 オブジェクトは、コンストラクターの呼び出しから、実行がコンストラクターのコードブロックの最初のステートメントに実際に到達するポイントまでの間に実行されるコンパイラー生成コードによって初期化されます。

オブジェクト インスタンスが初期化されておらず、範囲チェックが ({SR+} ディレクティブによって) 有効になっている場合、オブジェクト インスタンスの仮想メソッドへの最初の呼び出しで実行時エラーが発生します。 範囲チェックが ({SR-} ディレクティブによって) 無効になっている場合、初期化されていないオブジェクトの仮想メソッドを最初に呼び出すと、予期しない動作が発生する可能性があります。

必須の初期化ルールは、構造体タイプのコンポーネントであるインスタンスにも適用されます。 例えば:

VAR

コメント: TStrField の配列 [1..5]。

I:整数

始まる

私のために:= 1〜5

コメント[I].Init(1、I + 10、40、'first_name');

.

.

.

for I:= 1〜5doコメント[I].Done;

終わり

動的インスタンスの場合、初期化は通常、配置に関するものであり、クリーンアップは削除に関するものです。これは、NewおよびDispose標準プロシージャの拡張構文によって実現されます。 例えば:

VAR

SP:StrFieldPtr;

始まる

New(SP、Init(1、1、25、'first_name');

SP^.Put('ウラジミール');

SP^.表示;

.

.

.

処分(SP、完了);

終わり。

オブジェクトタイプへのポインタは、任意の親オブジェクトタイプへのポインタと割り当て互換であるため、実行時に、オブジェクトタイプへのポインタは、そのタイプのインスタンスまたは任意の子タイプのインスタンスを指すことができます。

たとえば、タイプZipFieldPtrのポインターは、タイプPZipField、PNField、およびPFieldのポインターに割り当てることができ、実行時に、タイプPFieldのポインターは、nilにするか、TField、TNumField、またはTZipFieldのインスタンスを指すことができます。 TFieldの子タイプのインスタンス。

これらの割り当てポインタの互換性ルールは、オブジェクトタイプパラメータにも適用されます。 たとえば、TField.Copメソッドには、TField、TStrField、TNumField、TZipField、またはTFieldの他のタイプの子のインスタンスを渡すことができます。

4. コンポーネントと範囲

Bean 識別子の範囲は、オブジェクト タイプを超えて拡張されます。 さらに、Bean 識別子の範囲は、オブジェクト型とその子孫のメソッドを実装するプロシージャ、関数、コンストラクタ、およびデストラクタのブロックにまで及びます。 これらの考慮事項に基づいて、コンポーネント識別子のスペルは、オブジェクト タイプ内、そのすべての子孫内、およびそのすべてのメソッド内で一意である必要があります。

型宣言のプライベート部分に記述されているコンポーネント識別子のスコープは、オブジェクト型宣言を含むモジュール(プログラム)に限定されています。 つまり、プライベートID Beanは、オブジェクト型宣言を含むモジュール内では通常のパブリックIDのように機能し、モジュール外では、プライベートBeanとIDは不明であり、アクセスできません。 関連するタイプのオブジェクトを同じモジュールに配置することで、これらのオブジェクトが互いのプライベートコンポーネントにアクセスできるようにし、これらのプライベートコンポーネントが他のモジュールに認識されないようにすることができます。

オブジェクト型宣言では、宣言がまだ完了していない場合でも、メソッドヘッダーで記述されているオブジェクト型のパラメータを指定できます。

LECTURE No. 12. 方法

1. 方法

オブジェクト型内のメソッド宣言は、前方メソッド宣言 (forward) に対応します。 したがって、オブジェクト型宣言の後のどこかで、ただしオブジェクト型宣言のスコープと同じスコープ内で、その宣言を定義することによってメソッドを実装する必要があります。

手続き型および関数型メソッドの場合、定義宣言は通常の手続きまたは関数宣言の形式を取りますが、この場合、手続きまたは関数識別子はメソッド識別子として扱われます。

コンストラクタおよびデストラクタメソッドの場合、定義宣言は、予約語のプロシージャが予約語のコンストラクタまたはデストラクタに置き換えられることを除いて、プロシージャメソッド宣言の形式を取ります。

定義するメソッド宣言は、オブジェクトタイプのメソッドヘッダーの正式なパラメーターのリストを繰り返すことができますが、必ずしもそうする必要はありません。 この場合、メソッドヘッダーは、オブジェクトタイプのヘッダー、順序、タイプ、パラメーター名、およびメソッドが関数の場合は関数結果の戻りタイプのヘッダーと正確に一致する必要があります。

メソッドの定義記述には、オブジェクト型を持つ仮変数パラメーターに対応する識別子 Self を持つ暗黙パラメーターが常に含まれます。 メソッド ブロック内で、 Self は、メソッド コンポーネントがメソッドを呼び出すように指定されたインスタンスを表します。 したがって、Self フィールドの値に対する変更は、インスタンスに反映されます。

オブジェクト型 Bean 識別子の範囲は、そのオブジェクト型のメソッドを実装するプロシージャ、関数、コンストラクタ、およびデストラクタのブロックにまで及びます。 この効果は、次の形式の with ステートメントがメソッド ブロックの先頭に挿入された場合と同じです。

自分でやる

始まる

...

終わり

これらの考慮事項に基づいて、コンポーネント識別子、正式なメソッド パラメーター、Self、およびメソッドの実行可能部分に導入される識別子のスペルは一意である必要があります。

一意のメソッド識別子が必要な場合は、修飾されたメソッド識別子が使用されます。 これは、オブジェクトタイプ識別子とそれに続くドットおよびメソッド識別子で構成されます。 他の識別子と同様に、修飾されたメソッド識別子の前には、オプションでパッケージ識別子とピリオドを付けることができます。

仮想メソッド

メソッドはデフォルトで静的ですが、コンストラクターを除いて、仮想にすることができます(メソッド宣言に仮想ディレクティブを含めることにより)。 コンパイラは、コンパイルプロセス中に静的メソッド呼び出しへの参照を解決しますが、仮想メソッド呼び出しは実行時に解決されます。 これは、遅延バインディングと呼ばれることもあります。

オブジェクトタイプが仮想メソッドを宣言または継承する場合、そのタイプの変数は、仮想メソッドを呼び出す前にコンストラクターを呼び出すことによって初期化する必要があります。 したがって、仮想メソッドを記述または継承するオブジェクトタイプは、少なくともXNUMXつのコンストラクタメソッドも記述または継承する必要があります。

オブジェクト型は、親から継承したメソッドをオーバーライドできます。 子のメソッド宣言が親のメソッド宣言と同じメソッド識別子を指定する場合、子の宣言は親の宣言をオーバーライドします。 オーバーライド メソッドのスコープは、メソッドが導入された子のスコープに拡張され、メソッド識別子が再度オーバーライドされるまでそのままになります。

静的メソッドのオーバーライドは、メソッド ヘッダーの変更とは無関係です。 対照的に、仮想メソッドのオーバーライドでは、順序、パラメーターの型と名前、および関数の結果の型 (存在する場合) を保持する必要があります。 さらに、再定義には virtual ディレクティブを再度含める必要があります。

動的メソッド

Borland Pascal は、動的メソッドと呼ばれる追加の遅延バインド メソッドをサポートしています。 動的メソッドは、実行時にディスパッチされる方法のみが仮想メソッドと異なります。 他のすべての点で、動的メソッドは仮想メソッドと同等と見なされます。

動的メソッド宣言は仮想メソッド宣言と同等ですが、動的メソッド宣言には、virtualキーワードの直後に指定される動的メソッドインデックスを含める必要があります。 動的メソッドのインデックスは、1〜656535の整数定数である必要があり、オブジェクトタイプまたはその祖先に含まれる他の動的メソッドのインデックス間で一意である必要があります。 例えば:

プロシージャFileOpen(var Msg:TMessage); 仮想100;

動的メソッドのオーバーライドは、パラメーターの順序、タイプ、および名前と一致し、親メソッドの関数の結果タイプと正確に一致する必要があります。 オーバーライドには、仮想ディレクティブと、それに続く祖先オブジェクトタイプで指定されたものと同じ動的メソッドインデックスも含める必要があります。

2. コンストラクタとデストラクタ

コンストラクタとデストラクタは、特殊な形式のメソッドです。 NewおよびDispose標準プロシージャの拡張構文に関連して使用されるコンストラクタとデストラクタには、動的オブジェクトを配置および削除する機能があります。 さらに、コンストラクターには、仮想メソッドを含むオブジェクトの必要な初期化を実行する機能があります。 すべてのメソッドと同様に、コンストラクタとデストラクタは継承でき、オブジェクトには任意の数のコンストラクタとデストラクタを含めることができます。

コンストラクターは、新しく作成されたオブジェクトを初期化するために使用されます。 通常、初期化は、パラメーターとしてコンストラクターに渡される値に基づいています。 仮想メソッドのディスパッチメカニズムは、オブジェクトを最初に初期化したコンストラクターに依存するため、コンストラクターを仮想にすることはできません。

コンストラクターの例を次に示します。

コンストラクタ Field.Copy(var F: フィールド);

始まる

自己:= F;

終わり

コンストラクタ Field.Init(FX, FY, FLen: 整数; FName: 文字列);

始まる

X := FX;

Y:= FY;

GetMem(Name、Length(FName)+ 1);

名前^ := FName;

終わり

コンストラクターTStrField.Init(FX、FY、FLen:整数; FName:文字列);

始まる

継承された Init(FX、FY、FLen、FName);

Field.Init(FX、FY、FLen、FName);

GetMem(値、長さ);

値^:='';

終わり

上記の TStr フィールドなど、派生 (子) 型のコンストラクターのメイン アクション。 init はほとんどの場合、直接の親の適切なコンストラクターを呼び出して、オブジェクトの継承されたフィールドを初期化します。 このプロシージャを実行した後、コンストラクタは派生型にのみ属するオブジェクトのフィールドを初期化します。

デストラクタはコンストラクタの反対であり、オブジェクトが使用された後にクリーンアップするために使用されます。 通常、クリーンアップでは、オブジェクト内のすべてのポインター フィールドを削除します。

注意

デストラクタは仮想にすることができ、多くの場合そうです。 デストラクタがパラメータを持つことはめったにありません。

デストラクタの例を次に示します。

デストラクタ フィールド Done;

始まる

FreeMem(Name、Length(Name ^)+ 1);

終わり

デストラクタ StrField.Done;

始まる

FreeMem(Value、Len);

フィールド完了;

終わり

上記の TStrField などの子型のデストラクタ。 通常、最初に派生型で導入されたポインター フィールドを削除し、最後のステップとして、直接の親の適切なコレクター/デストラクターを呼び出して、オブジェクトの継承されたポインター フィールドを削除します。

3.デストラクタ

Borland Pascalは、動的に割り当てられたオブジェクトをクリーンアップおよび削除するためのガベージコレクタ(またはデストラクタ)と呼ばれる特別なタイプのメソッドを提供します。 デストラクタは、オブジェクトを削除するステップと、そのタイプのオブジェクトに必要な他のアクションまたはタスクを組み合わせたものです。 XNUMXつのオブジェクトタイプに対して複数のデストラクタを定義できます。

デストラクタは、オブジェクトの型定義で他のすべてのオブジェクト メソッドとともに定義されます。

タイプ

従業員 = オブジェクト

名前: 文字列[25];

タイトル: 文字列[25];

レート: レアル;

コンストラクター Init(AName, ATitle: String; ARate: Real);

デストラクタ 完了; バーチャル;

関数GetName:文字列;

関数 GetTitle: 文字列;

関数 GetRate: レート; バーチャル;

関数 GetPayAmount: 実数; バーチャル;

終わり

デストラクタは継承でき、静的または仮想のいずれかになります。 ファイナライザーが異なれば、必要なオブジェクトのタイプも異なる傾向があるため、オブジェクトのタイプごとに正しいデストラクタが実行されるように、デストラクタは常に仮想であることが一般的に推奨されます。

オブジェクトの型定義に仮想メソッドが含まれている場合でも、すべてのクリーンアップ メソッドに対して予約語デストラクタを指定する必要はありません。 デストラクタは、実際には動的に割り当てられたオブジェクトに対してのみ機能します。

動的に割り当てられたオブジェクトがクリーンアップされると、デストラクタは特別な機能を実行します。つまり、動的に割り当てられたメモリ領域で正しいバイト数が常に解放されるようにします。 静的に割り当てられたオブジェクトでデストラクタを使用することについて心配する必要はありません。 実際、オブジェクトの型をデストラクタに渡さないことで、プログラマはその型のオブジェクトから、Borland Pascal の動的メモリ管理の利点を完全に享受できなくなります。

デストラクタは、ポリモーフィック オブジェクトをクリアする必要がある場合や、オブジェクトが占有するメモリの割り当てを解除する必要がある場合に、実際に自分自身になります。

ポリモーフィックオブジェクトは、BorlandPascalの拡張型互換性ルールのために親型に割り当てられたオブジェクトです。 タイプTEmployeeの変数に割り当てられたタイプTHourlyのオブジェクトのインスタンスは、ポリモーフィックオブジェクトの例です。 これらのルールはオブジェクトにも適用できます。 THourlyへのポインターは、TEmployeeへのポインターに自由に割り当てることができ、そのポインターが指すオブジェクトは、再びポリモーフィックオブジェクトになります。 オブジェクトを処理するコードは、コンパイル時に正確にどのタイプのオブジェクトを処理する必要があるかを正確に「知らない」ため、「ポリモーフィック」という用語が適切です。 それが知っている唯一のことは、このオブジェクトが指定されたオブジェクトタイプの子孫であるオブジェクトの階層に属しているということです。

明らかに、オブジェクト タイプのサイズは異なります。 では、ヒープに割り当てられたポリモーフィック オブジェクトをクリーンアップするときが来たら、Dispose は解放するヒープ領域のバイト数をどのように知るのでしょうか? コンパイル時には、オブジェクトのサイズに関する情報をポリモーフィック オブジェクトから抽出することはできません。

デストラクタは、この情報が書き込まれている場所 (TCM 実装変数) を参照することで、このパズルを解決します。 オブジェクト タイプの各 TBM には、そのオブジェクト タイプのバイト単位のサイズが含まれます。 任意のオブジェクトの仮想メソッドのテーブルは、メソッドが呼び出されたときにメソッドに送信される隠しパラメーター Self を介して利用できます。 デストラクタは単なるメソッドの一種であるため、オブジェクトがそれを呼び出すと、デストラクタはスタック上の Self のコピーを取得します。 したがって、コンパイル時にオブジェクトがポリモーフィックである場合、遅延バインディングのために実行時にポリモーフィックになることはありません。

このレイト バインド解除を実行するには、Dispose プロシージャの拡張構文の一部としてデストラクタを呼び出す必要があります。

Dispose(P, 完了);

(Disposeプロシージャの外部でデストラクタを呼び出しても、メモリの割り当てはまったく解除されません。)ここで実際に発生しているのは、Pが指すオブジェクトのガベージコレクタが通常のメソッドとして実行されることです。 ただし、最後のアクションが完了すると、デストラクタはTCMでそのタイプの実装のサイズを検索し、そのサイズをDisposeプロシージャに渡します。 Disposeプロシージャは、以前はP ^に属していたヒープスペース(スペース)の正しいバイト数を削除することにより、プロセスを終了します。 解放されるバイト数は、PがTSalariedタイプのインスタンスを指しているかどうか、またはTCommissionedなどのTSalariedタイプの子タイプのXNUMXつを指しているかどうかに関係なく正しくなります。

デストラクタ メソッド自体は空で、この機能のみを実行できることに注意してください。

destructorAnObject.Done;

始まる

終わり

このデストラクタで役立つのは、その本体のプロパティではありませんが、コンパイラは、デストラクタの予約語に応答してエピローグ コードを生成します。 これは、何もエクスポートしないモジュールのようなものですが、プログラムを開始する前に初期化セクションを実行することで、目に見えない作業を行います。 すべてのアクションは舞台裏で行われます。

4.仮想メソッド

オブジェクト型宣言の後に新しい予約語 virtual が続く場合、メソッドは virtual になります。 親型のメソッドが virtual として宣言されている場合、コンパイラ エラーを回避するために、子型の同じ名前を持つすべてのメソッドも virtual として宣言する必要があります。

以下は、適切に仮想化された給与計算の例のオブジェクトです。

タイプ

PEmployee = ^TEmployee;

従業員 = オブジェクト

名前、タイトル: string[25];

レート: レアル;

コンストラクターInit(AName、ATitle:String; ARate:Real);

関数 GetPayAmount : 実数; バーチャル;

関数 GetName : 文字列;

関数 GetTitle : 文字列;

関数 GetRate : 実数;

手順表示; バーチャル;

終わり

PH 時間 = ^TH 時間;

THourly = object(TEmployee);

時間: 整数。

コンストラクター Init(AName, ATitle: String; ARate: Real; Time: Integer);

関数 GetPayAmount : 実数; バーチャル;

関数GetTime:整数;

終わり

PSalaried = ^TSalaried;

TSalaried = オブジェクト(従業員);

関数 GetPayAmount : 実数; バーチャル;

終わり

PCommissioned = ^ TCommissioned;

TCommissioned = object(サラリーマン);

手数料 : リアル;

売上高 : 実質;

コンストラクターInit(AName、ATitle:String; ARate、

ACommission、ASalesAmount: Real);

関数 GetPayAmount : 実数; バーチャル;

終わり

コンストラクターは、仮想メソッド機構のセットアップ作業を行う特殊なタイプのプロシージャーです。 さらに、仮想メソッドが呼び出される前に、コンストラクターを呼び出す必要があります。 最初にコンストラクターを呼び出さずに仮想メソッドを呼び出すと、システムがブロックされる可能性があり、コンパイラーはメソッドが呼び出される順序をチェックする方法がありません。

仮想メソッドを持つすべてのオブジェクト型には、コンストラクターが必要です。

警告

コンストラクターは、他の仮想メソッドを呼び出す前に呼び出す必要があります。 コンストラクターを事前に呼び出さずに仮想メソッドを呼び出すと、システムロックが発生する可能性があり、コンパイラーはメソッドが呼び出される順序を確認できません。

注意

オブジェクト コンストラクターの場合、識別子 Init を使用することをお勧めします。

個別のオブジェクト インスタンスはそれぞれ、個別のコンストラクター呼び出しで初期化する必要があります。 オブジェクトの XNUMX つのインスタンスを初期化してから、そのインスタンスを他のインスタンスに割り当てるだけでは十分ではありません。 他のインスタンスは、有効なデータが含まれている場合でも、代入演算子で初期化されず、仮想メソッドの呼び出しでシステムをブロックします。 例えば:

VAR

FBee、GBee:蜂; {XNUMXつのBeeインスタンスを作成する}

始まる

FBee.Init(5, 9) { FBee のコンストラクタ呼び出し }

GBee := FBee; { Gbee は無効です! }

終わり

コンストラクターは正確に何を作成しますか? 各オブジェクト タイプには、データ セグメントに仮想メソッド テーブル (VMT) と呼ばれるものが含まれています。 TVM には、オブジェクト型のサイズと、仮想メソッドごとに、そのメソッドを実行するコードへのポインターが含まれます。 コンストラクターは、オブジェクトの呼び出し元の実装とオブジェクトの型 TCM の間の関係を確立します。

オブジェクトタイプごとにTBMがXNUMXつしかないことを覚えておくことが重要です。 オブジェクトタイプの個別のインスタンス(つまり、このタイプの変数)には、TBMへの接続のみが含まれ、TBM自体は含まれません。 コンストラクターは、この接続の値をTBMに設定します。 このため、コンストラクターを呼び出す前に実行を開始することはできません。

5.オブジェクトデータフィールドと形式手法パラメーター

メソッドとそのオブジェクトが共通のスコープを共有するという事実の意味は、メソッドの仮パラメーターがオブジェクトのデータ フィールドのいずれかと同一であってはならないということです。 これは、オブジェクト指向プログラミングによって課せられた新しい制限ではなく、Pascal が常に持っていた同じ古いスコープ規則です。 これは、プロシージャの仮パラメータがプロシージャのローカル変数と同一になるのを防ぐことと同じです。

プロシージャ CrunchIt(Cru​​nchee: MyDataRec、Crunchby、

エラー コード: 整数);

VAR

A、B: 文字。

エラー コード: 整数;

始まる

.

.

.

プロシージャのローカル変数とその仮パラメータは共通のスコープを共有しているため、同一にすることはできません。 このようなものをコンパイルしようとすると、「エラー4:重複した識別子」が表示されます。正式なメソッドパラメータを、メソッドが属するオブジェクトのフィールドの名前に設定しようとすると、同じエラーが発生します。

プロシージャヘッダーをデータ構造内に配置することはTurboPascalの革新に賛成であるため、状況は多少異なりますが、Pascalスコープの基本原則は変更されていません。

レクチャーNo.13。オブジェクトタイプの互換性

1.カプセル化

オブジェクト内のコードとデータの組み合わせは、カプセル化と呼ばれます。 原則として、オブジェクトのユーザーがオブジェクトのフィールドに直接アクセスしないように、十分なメソッドを提供することができます。 Smalltalkなど、他のオブジェクト指向言語には必須のカプセル化が必要ですが、BorlandPascalには選択肢があります。

たとえば、TEmployee および THourly オブジェクトは、内部データ フィールドに直接アクセスする必要がまったくないように記述されています。

type

従業員 = オブジェクト

名前、タイトル: string[25];

レート: レアル;

プロシージャInit(AName、ATitle:string; ARate:Real);

関数 GetName : 文字列;

関数 GetTitle : 文字列;

関数 GetRate : 実数;

関数 GetPayAmount : 実数;

終わり

THourly = オブジェクト(TEmployee)

時間: 整数。

プロシージャInit(AName、ATitle:string; ARate:

Real、Atime: 整数);

関数 GetPayAmount : 実数;

終わり

ここには、名前、タイトル、レート、時間の XNUMX つのデータ フィールドしかありません。 GetName メソッドと GetTitle メソッドは、それぞれ従業員の姓と役職を表示します。 GetPayAmount メソッドは Rate を使用し、稼働中の場合は THourly と Time を使用して稼働中への支払い額を計算します。 これらのデータ フィールドを直接参照する必要はなくなりました。

THourly 型の AnHourly インスタンスが存在すると仮定すると、一連のメソッドを使用して、次のように AnHourly データ フィールドを操作できます。

時給付き

始まる

初期化 (アレクサンドル ペトロフ、フォーク リフト オペレーター' 12.95、62);

{姓、役職、支払い額を表示します}

ショー;

終わり

オブジェクトのフィールドへのアクセスは、このオブジェクトのメソッドを使用してのみ実行されることに注意してください。

2. オブジェクトの拡張

残念ながら、標準の Pascal には、まったく異なるデータ型を操作できる柔軟な手順を作成する機能がありません。 オブジェクト指向プログラミングは、継承によってこの問題を解決します。派生型が定義されている場合、親型のメソッドは継承されますが、必要に応じてオーバーライドできます。 継承されたメソッドをオーバーライドするには、継承されたメソッドと同じ名前で新しいメソッドを宣言するだけですが、本体と (必要に応じて) パラメーターのセットが異なります。

次の例で、時給が支払われる従業員を表す TEmployee の子型を定義しましょう。

定数

支払期間 = 26; 《お支払い期間》

残業しきい値 = 80; 《お支払い期間について》

残業係数 = 1.5; { 時給 }

type

THourly = オブジェクト(TEmployee)

時間: 整数。

プロシージャInit(AName、ATitle:string; ARate:

Real、Atime: 整数);

関数 GetPayAmount : 実数;

終わり

プロシージャ THourly.Init(AName, ATitle: string;

ARate: 実数、Atime: 整数);

始まる

TEmployee.Init(AName, ATitle, ARate);

時間:= ATime;

終わり

関数 THourly.GetPayAmount: リアル;

VAR

残業:整数;

始まる

残業:= 時間 - OvertimeThreshold;

残業>0の場合

GetPayAmount:= RoundPay(OvertimeThreshold*レート+

レート残業 * 残業係数 * レート)

ほかに

GetPayAmount := RoundPay(時間 * レート)

終わり

時間給を支払われる人は労働者です。彼はTEmployeeオブジェクト(名前、位置、率)を定義するために使用されるすべてのものを持っており、時間給を受け取る金額だけが彼が何時間働いたかに依存します支払われる期間。 したがって、THourlyには時間フィールドも必要です。

THourly は新しい Time フィールドを定義するため、その初期化には、時間と継承されたフィールドの両方を初期化する新しい Init メソッドが必要です。 Name、Title、Rate などの継承されたフィールドに値を直接割り当てる代わりに、TEmployee オブジェクトの初期化メソッドを再利用してみませんか (最初の THourly Init ステートメントで示されています)。

オーバーライドされているメソッドを呼び出すことは、最適なスタイルではありません。 一般に、TEmployee.Init は重要だが隠れた初期化を実行する可能性があります。

オーバーライドされたメソッドを呼び出すときは、派生オブジェクト タイプに親の機能が含まれていることを確認する必要があります。 さらに、親メソッドを変更すると、すべての子メソッドが自動的に影響を受けます。

TEmployee.Init を呼び出した後、THourly.Init は独自の初期化を実行できます。この場合は、ATime で渡された値を割り当てるだけです。

オーバーライドされたメソッドのもう XNUMX つの例は、THourly.GetPayAmount 関数です。これは、時間給の従業員の支払い額を計算します。 実際、TEmployee オブジェクトの各タイプには独自の GetPayAmount メソッドがあります。これは、ワーカーのタイプが計算方法に依存するためです。 THourly.GetPayAmount メソッドでは、従業員の勤務時間、残業の有無、残業の増加要因などを考慮する必要があります。

Tサラリード方式。 GetPayAmount は、従業員のレートを毎年の支払い回数で割るだけです (この例では)。

単位労働者;

インタフェース

定数

支払期間 = 26; {年に}

残業しきい値=80; {支払い期間ごと}

残業係数=1.5; {通常の支払いに対する増加}

type

従業員 = オブジェクト

名前、タイトル: string[25];

レート: レアル;

プロシージャInit(AName、ATitle:string; ARate:Real);

関数 GetName : 文字列;

関数 GetTitle : 文字列;

関数 GetRate : 実数;

関数 GetPayAmount : 実数;

終わり

THourly = オブジェクト(TEmployee)

時間: 整数。

プロシージャInit(AName、ATitle:string; ARate:

Real、Atime: 整数);

関数 GetPayAmount : 実数;

関数GetTime:実数;

終わり

TSalried = object(TEmployee)

関数 GetPayAmount : 実数;

終わり

TCommissioned = object(TSalaried)

手数料 : リアル;

売上高 : 実質;

コンストラクターInit(AName、ATitle:String; ARate、

ACommission、ASalesAmount: Real);

関数 GetPayAmount : 実数;

終わり

実装

関数 RoundPay(賃金: 実質) : 実質;

{支払いを切り上げて、以下の金額を無視する

通貨単位}

始まる

RoundPay := Trunc(賃金 * 100) / 100;

.

.

.

TEmployeeはオ​​ブジェクト階層の最上位であり、最初のGetPayAmountメソッドが含まれています。

関数 TEmployee.GetPayAmount : 実数;

始まる

RunError(211); {ランタイムエラーを与える}

終わり

メソッドで実行時エラーが発生することに驚くかもしれません。 Employee.GetPayAmount を呼び出すと、プログラムでエラーが発生します。 なんで? TEmployee はオブジェクト階層の最上位であり、実際のワーカーを定義していないためです。 したがって、TEmployee メソッドは継承される可能性がありますが、特定の方法で呼び出されることはありません。 当社の従業員は全員、時給制、月給制、出来高払いのいずれかです。 実行時エラーが発生すると、プログラムの実行が終了し、211 が出力されます。これは、抽象メソッドの呼び出しに関連するエラー メッセージに対応します (プログラムが誤って TEmployee.GetPayAmount を呼び出した場合)。

以下はTHourly.GetPayAmountメソッドで、残業代、労働時間などを考慮に入れています。

関数 THourly.GetPayAMount : リアル;

VAR

残業: 整数;

始まる

残業:= 時間 - OvertimeThreshold;

残業>0の場合

GetPayAmount:= RoundPay(OvertimeThreshold*レート+

レート残業 * 残業係数 * レート)

ほかに

GetPayAmount := RoundPay(時間 * レート)

終わり

TSalaried.GetPayAmountメソッドははるかに単純です。 それに賭ける

支払い回数で割った値:

関数TSalaried.GetPayAmount:実数;

始まる

GetPayAmount:= RoundPay(Rate / PayPeriods);

終わり

TCommissioned.GetPayAmountメソッドを見ると、TSalaried.GetPayAmountが呼び出され、手数料が計算され、TSalariedメソッドによって返される値に追加されていることがわかります。 GetPayAmount。

関数 TCommissioned.GetPayAmount : 実数;

始まる

GetPayAmount:= RoundPay(TSalaried.GetPayAmount +

手数料 * 売上高);

終わり

重要な注意: メソッドはオーバーライドできますが、データ フィールドはオーバーライドできません。 オブジェクト階層でデータ フィールドが定義されると、まったく同じ名前のデータ フィールドを子型で定義することはできません。

3. オブジェクトタイプの互換性

継承は、Borland Pascal の型互換性規則をある程度変更します。 特に、派生型はすべての親型の型の互換性を継承します。

この拡張タイプの互換性には、次のXNUMXつの形式があります。

1) オブジェクトの実装間。

2)オブジェクト実装へのポインタ間。

3) 仮パラメータと実際のパラメータの間。

ただし、XNUMX つの形式すべてにおいて、型の互換性は子から親にのみ及ぶことを覚えておくことが非常に重要です。 つまり、子の型は親の型の代わりに自由に使用できますが、その逆はできません。

たとえば、TSalariedはTEmployeeの子であり、TSosh-missionedはTSalariedの子です。 そのことを念頭に置いて、次の説明を検討してください。

タイプ

PEmployee = ^TEmployee;

PSalaried = ^TSalaried;

PCommissioned = ^ TCommissioned;

VAR

AnEmployee: TEmployee;

ASalaried:TSalaried;

Pコミッショニング: Tコミッショニング;

TEmployeePtr: PEmployee;

TSalariedPtr: PSalaried;

TCommissionedPtr: PCommissioned;

これらの説明では、次の演算子が有効です

課題:

AnEmployee :=ASサラリード;

A給与 := A委託;

TCommissionedPtr := ACommissioned;

注意

親オブジェクトには、その派生型のインスタンスを割り当てることができます。 バックアサインメントは許可されていません。

この概念は Pascal にとって新しいものであり、最初はどのような順序型の互換性があるかを思い出すのが難しいかもしれません。 次のように考える必要があります。ソースはレシーバーを完全に満たすことができなければなりません。 派生型には、継承のプロパティにより、親型に含まれるすべてのものが含まれます。 したがって、派生型はまったく同じサイズであるか、(ほとんどの場合) 親よりも大きいが、決して小さくはありません。 親 (親) オブジェクトを子 (子) に割り当てると、子オブジェクトの一部のフィールドが未定義のままになる可能性があり、これは危険であり、違法です。

割り当てステートメントでは、両方のタイプに共通のフィールドのみがソースから宛先にコピーされます。 代入演算子の場合:

AnEmployee:= ACommissioned;

TCommissioned と TEmployee に共通する唯一のフィールドであるため、ACommissioned の Name、Title、および Rate フィールドのみが AnEmployee にコピーされます。 型の互換性は、オブジェクト型へのポインター間でも機能し、オブジェクトの実装と同じ一般規則に従います。 子へのポインタは、親へのポインタに割り当てることができます。 前の定義を考えると、次のポインター割り当てが有効です。

TSalariedPtr:= TCommissionedPtr;

TEmployeePtr:= TSalariedPtr;

TEmployeePtr:= PCommissionedPtr;

逆割り当ては許可されていないことに注意してください。

特定のオブジェクト型の仮パラメーター (値または変数パラメーターのいずれか) は、その実パラメーターとして、それ自身の型のオブジェクトまたはすべての子型のオブジェクトを取ることができます。 次のようなプロシージャ ヘッダーを定義する場合:

プロシージャCalcFedTax(Victim:TSalaried);

この場合、実際のパラメータの型は TSalaried または TCommissioned にすることができますが、TEmployee にすることはできません。 Victim は可変パラメータにすることもできます。 この場合、同じ互換性ルールに従います。

注意:

値パラメーターと変数パラメーターには基本的な違いがあります。 値パラメーターはパラメーターとして渡される実際のオブジェクトへのポインターであり、変数パラメーターは実際のパラメーターの単なるコピーです。 さらに、このコピーには、仮の値パラメーターの型に含まれるフィールドのみが含まれます。 これは、実パラメータが文字どおり仮パラメータの型に変換されることを意味します。 変数パラメーターは、実際のパラメーターが変更されないという意味で、パターンへのキャストに似ています。

同様に、仮パラメータがオブジェクトタイプへのポインタである場合、実際のパラメータはそのオブジェクトタイプまたは任意の子タイプへのポインタにすることができます。 手順のタイトルを付けましょう:

プロシージャWorker.Add(AWorker:PSalared);

その場合、有効な実際のパラメータータイプはPSalariedまたはPCommissionedになりますが、PEmployeeではありません。

LECTURE No.14. アセンブラ

1. アセンブラについて

むかしむかし、アセンブラは、コンピューターに何かをさせることが不可能であることを知らずに言語でした。 徐々に状況が変わった。 コンピュータとのより便利な通信手段が登場しました。 しかし、他の言語とは異なり、アセンブラは死ぬことはなく、原則としてこれを行うことはできませんでした。 なんで? 答えを求めて、アセンブリ言語が一般的に何であるかを理解しようとします。

つまり、アセンブリ言語は機械語の記号表現です。 最下層のハードウェア レベルにあるマシンのすべてのプロセスは、マシン語のコマンド (命令) によってのみ駆動されます。 このことから、一般的な名前にもかかわらず、コンピューターの種類ごとにアセンブリ言語が異なることが明らかです。 これは、アセンブラで書かれたプログラムの外観、およびこの言語が反映しているアイデアにも当てはまります。

アセンブラの知識がなければ、ハードウェア関連の問題 (さらには、プログラムの高速化などのハードウェア関連の問題) を実際に解決することは不可能です。

プログラマーやその他のユーザーは、仮想世界を構築するためのプログラムまでの高水準ツールを使用できます。おそらく、コンピューターがプログラムが記述されている言語のコマンドではなく、変換された表現を実際に実行しているとは思わないでしょう。まったく異なる言語(機械語)の退屈で退屈なシーケンスコマンドの形で。 ここで、そのようなユーザーが非標準の問題を抱えていると想像してください。 たとえば、彼のプログラムは、いくつかの異常なデバイスで動作するか、コンピューターハードウェアの原理の知識を必要とする他のアクションを実行する必要があります。 プログラマーが自分のプログラムを書いた言語がどれほど優れていても、彼はアセンブラーを知らなければできません。 また、高級言語のほとんどすべてのコンパイラが、モジュールをアセンブラ内のモジュールに接続する手段を備えているか、アセンブラプログラミングレベルへのアクセスをサポートしているのは偶然ではありません。

コンピュータは複数の物理デバイスで構成されており、それぞれがシステム ユニットと呼ばれる 1 つのユニットに接続されています。 それらの機能的な目的を理解するために、典型的なコンピューターのブロック図を見てみましょう (図 XNUMX)。 絶対的な正確さを装うのではなく、現代のパーソナル コンピュータの要素の目的、関係、および典型的な構成を示すことのみを目的としています。

米。 1. パソコンの構造図

2. マイクロプロセッサのソフトウェアモデル

今日のコンピュータ市場には、さまざまな種類のコンピュータがあります。 したがって、消費者は、特定のタイプ (またはモデル) のコンピューターの機能と、他のタイプ (モデル) のコンピューターとは異なる特徴をどのように評価するかについて疑問を持つと想定できます。 プログラムによって制御される機能という点でコンピューターを特徴付けるすべての概念をまとめるために、コンピューター アーキテクチャという特別な用語があります。 コンピューター アーキテクチャの概念は、比較評価のために第 3 世代のマシンが登場したことで初めて言及され始めました。

コンピュータのどの部分が表示されたままで、この言語でプログラミングできるかを確認した後でのみ、任意のコンピュータのアセンブリ言語の学習を開始することは理にかなっています。 これはいわゆるコンピュータプログラムモデルであり、その一部はマイクロプロセッサプログラムモデルであり、プログラマが多かれ少なかれ使用できるXNUMX個のレジスタが含まれています。

これらのレジスタは、次のXNUMXつの大きなグループに分けることができます。

1)6のユーザーレジスタ。

2)16個のシステムレジスタ。

3. ユーザー登録

名前が示すように、ユーザーレジスターは、プログラマーがプログラムを作成するときに使用できるため、呼び出されます。 これらのレジスタには以下が含まれます (図 2)。

1) プログラマがデータとアドレスを格納するために使用できる 32 つの XNUMX ビット レジスタ (汎用レジスタ (RON) とも呼ばれます):

eax/ax/ah/al;

ebx / bx / bh / bl;

edx/dx/dh/dl;

ecx/cx/ch/cl;

ebp/bp;

esi/si;

エディ/ディ;

esp/sp。

2) XNUMX つのセグメント レジスタ: cs、ds、ss、es、fs、gs。

3) ステータスおよび制御レジスタ:

フラグレジスタ eflags/flags;

eip/ipコマンドポインタレジスタ。

米。 2. ユーザー登録

これらのレジスタの多くは、スラッシュで示されます。 これらは異なるレジスタではなく、32 つの大きな XNUMX ビット レジスタの一部です。 これらは、プログラム内で個別のオブジェクトとして使用できます。

4.一般的なレジスタ

このグループのすべてのレジスタを使用すると、「下位」部分にアクセスできます。 セルフアドレッシングに使用できるのは、これらのレジスタの下位16ビットおよび8ビット部分のみです。 これらのレジスタの上位16ビットは、独立したオブジェクトとして使用できません。

汎用レジスタのグループに属するレジスタをリストしてみましょう。 これらのレジスタは、算術論理演算装置(AL>)内のマイクロプロセッサに物理的に配置されているため、ALUレジスタとも呼ばれます。

1)eax / ax / ah / al(アキュムレータレジスタ)-バッテリ。 中間データを格納するために使用されます。 一部のコマンドでは、このレジスタの使用が必要です。

2)ebx / bx / bh / bl(ベースレジスタ)-ベースレジスタ。 オブジェクトのベースアドレスをメモリに格納するために使用されます。

3) ecx/cx/ch/cl (カウント レジスタ) - カウンタ レジスタ。 これは、いくつかの反復アクションを実行するコマンドで使用されます。 多くの場合、その使用は暗黙的であり、対応するコマンドのアルゴリズムに隠されています。

たとえば、ループ ループを編成するコマンドは、特定のアドレスにあるコマンドに制御を移すだけでなく、esx/cx レジスタの値を分析して XNUMX つ減らします。

4) edx/dx/dh/dl (データレジスタ) - データレジスタ。

eax/ax/ah/al レジスタと同様に、中間データを格納します。 一部のコマンドには、その使用が必要です。 一部のコマンドでは、これは暗黙的に発生します。

次の 32 つのレジスタは、いわゆるチェーン操作、つまり要素のチェーンを順次処理する操作をサポートするために使用されます。各要素のチェーンは 16、8、または XNUMX ビット長にすることができます。

1)esi / si(ソースインデックスレジスタ)-ソースインデックス。

チェーン操作のこのレジスタには、ソース チェーン内の要素の現在のアドレスが含まれます。

2)edi / di(宛先インデックスレジスタ)-受信者(受信者)のインデックス。 チェーン操作のこのレジスタには、宛先チェーンの現在のアドレスが含まれています。

ハードウェアおよびソフトウェアレベルのマイクロプロセッサのアーキテクチャでは、スタックなどのデータ構造がサポートされています。 マイクロプロセッサ命令システムのスタックを操作するために、特別なコマンドがあり、マイクロプロセッサソフトウェアモデルには、このための特別なレジスタがあります。

1) esp/sp (スタック ポインター レジスター) - スタック ポインター レジスター。 現在のスタック セグメントのスタックの先頭へのポインターが含まれます。

2)ebp / bp(ベースポインタレジスタ)-スタックフレームベースポインタレジスタ。 スタック内のデータへのランダムアクセスを整理するように設計されています。

一部の命令でレジスタのハード ピニングを使用すると、それらのマシン表現をよりコンパクトにエンコードできます。 これらの機能を知っていれば、必要に応じて、プログラム コードが占有するメモリを少なくとも数バイト節約できます。

5. セグメントレジスタ

マイクロプロセッサ ソフトウェア モデルには、cs、ss、ds、es、gs、fs の XNUMX つのセグメント レジスタがあります。

それらの存在は、組織の詳細とIntelマイクロプロセッサによるRAMの使用によるものです。 これは、マイクロプロセッサハードウェアが、セグメントと呼ばれるXNUMXつの部分の形でプログラムの構造的編成をサポートしているという事実にあります。 したがって、このようなメモリの編成はセグメント化と呼ばれます。

プログラムが特定の時点でアクセスできるセグメントを示すために、セグメントレジスタが意図されています。 実際(わずかに修正して)、これらのレジスタには、対応するセグメントが始まるメモリアドレスが含まれています。 マシン命令を処理するロジックは、命令のフェッチ、プログラムデータへのアクセス、またはスタックへのアクセス時に、明確に定義されたセグメントレジスタのアドレスが暗黙的に使用されるように構築されています。

マイクロプロセッサは、次のタイプのセグメントをサポートしています。

1. コード セグメント。 プログラム コマンドが含まれます。 このセグメントにアクセスするには、cs レジスタ (コード セグメント レジスタ) - セグメント コード レジスタが使用されます。 これには、マイクロプロセッサがアクセスできるマシン命令セグメントのアドレスが含まれています (つまり、これらの命令はマイクロプロセッサ パイプラインにロードされます)。

2. データ セグメント。 プログラムによって処理されたデータが含まれます。 このセグメントにアクセスするには、ds レジスタ (データ セグメント レジスタ) が使用されます。これは、現在のプログラムのデータ セグメントのアドレスを格納するセグメント データ レジスタです。

3.スタックセグメント。 このセグメントは、スタックと呼ばれるメモリの領域です。 マイクロプロセッサは、次の原則に従ってスタックでの作業を編成します。この領域に書き込まれた最後の要素が最初に選択されます。 このセグメントにアクセスするには、ssレジスタ(スタックセグメントレジスタ)を使用します。これは、スタックセグメントのアドレスを含むスタックセグメントレジスタです。

4. 追加のデータ セグメント。 ほとんどのマシン命令を実行するためのアルゴリズムは、処理するデータがデータ セグメントにあり、そのアドレスが ds セグメント レジスタにあることを暗に想定しています。 XNUMX つのデータ セグメントがプログラムに十分でない場合、さらに XNUMX つの追加のデータ セグメントを使用する機会があります。 ただし、アドレスが ds セグメント レジスタに含まれるメイン データ セグメントとは異なり、追加のデータ セグメントを使用する場合は、コマンドで特別なセグメント再定義プレフィックスを使用して、それらのアドレスを明示的に指定する必要があります。 追加のデータ セグメントのアドレスは、レジスタ es、gs、fs (拡張データ セグメント レジスタ) に含まれている必要があります。

6. ステータスおよび制御レジスタ

マイクロプロセッサには、マイクロプロセッサ自体と、現在パイプラインに命令がロードされているプログラムの両方の状態に関する情報を常に含むいくつかのレジスタが含まれています。 これらのレジスタには次のものがあります。

1) フラグレジスタ eflags/flags;

2) eip/ip コマンド ポインタ レジスタ。

これらのレジスタを使用して、コマンド実行の結果に関する情報を取得し、マイクロプロセッサ自体の状態に影響を与えることができます。 これらのレジスタの目的と内容をさらに詳しく考えてみましょう。

1. eflags / flags(フラグレジスタ)-フラグレジスタ。 eflags/flagsのビット深度は32/16ビットです。 このレジスタの個々のビットには特定の機能目的があり、フラグと呼ばれます。 このレジスタの下部は、18086のフラグレジスタとまったく同じです。図3は、eflagsレジスタの内容を示しています。

米。 3. eflags レジスタの内容

eflags/flags レジスタのフラグは、使用方法に応じて XNUMX つのグループに分けられます。

1) XNUMX つのステータスフラグ。

これらのフラグは、マシン命令が実行された後に変更される場合があります。 eflags レジスタのステータス フラグは、算術演算または論理演算の実行結果の詳細を反映します。 これにより、計算プロセスの状態を分析し、条件付きジャンプ コマンドとサブルーチン呼び出しを使用して応答することができます。 表 1 に、ステータス フラグとその目的を示します。

2) XNUMX つの制御フラグ。

df (ディレクトリ フラグ) と表示されます。 これは eflags レジスタのビット 10 にあり、チェーン コマンドで使用されます。 df フラグの値は、これらの操作における要素ごとの処理の方向を決定します: 文字列の先頭から末尾 (df = 0) またはその逆、文字列の末尾から先頭 (df = 1)。 df フラグを操作するための特別なコマンドがあります: eld (df フラグを削除) および std (df フラグを設定)。 これらのコマンドを使用すると、アルゴリズムに従って df フラグを調整し、文字列に対して操作を実行するときにカウンターが自動的に増分または減分されるようにすることができます。

3) XNUMX つのシステム フラグ。

I/O、マスカブル割り込み、デバッグ、タスク切り替え、および 8086 仮想モードを制御します. アプリケーション プログラムがこれらのフラグを不必要に変更することはお勧めできません. 表 2 に、システム フラグとその目的を示します。

表 1. ステータス フラグ表 2. システム フラグ

2. eip/ip (命令ポインタ レジスタ) - 命令ポインタ レジスタ。 eip/ip レジスタは 32/16 ビット幅で、現在の命令セグメントの cs セグメント レジスタの内容に関連して実行される次の命令のオフセットが含まれています。 プログラマーはこのレジスターに直接アクセスできませんが、その値はさまざまな制御コマンドによってロードおよび変更されます。これには、条件付きおよび無条件のジャンプ、プロシージャーの呼び出し、およびプロシージャーからの復帰のためのコマンドが含まれます。 割り込みが発生すると、eip/ip レジスタも変更されます。

LECTURE No. 15. レジスター

1.マイクロプロセッサシステムレジスタ

これらのレジスタの名前は、システム内で特定の機能を実行することを示唆しています。 システム レジスタの使用は厳密に規制されています。 プロテクトモードを提供するのは彼らです。 これらは、資格のあるシステム プログラマが最も低レベルの操作を実行できるように、意図的に可視化されたマイクロプロセッサ アーキテクチャの一部と考えることができます。

システムレジスタは、次のXNUMXつのグループに分けることができます。

1)XNUMXつの制御レジスタ。

2)システムアドレスのXNUMXつのレジスタ。

3) XNUMX つのデバッグ レジスタ。

2. 制御レジスタ

制御レジスタのグループには、cr0、cr1、cr2、cr3の0つのレジスタが含まれます。 これらのレジスタは、一般的なシステム制御用です。 制御レジスタは、特権レベルXNUMXのプログラムでのみ使用できます。

マイクロプロセッサには1つの制御レジスタがありますが、使用できるのはXNUMXつだけです。crXNUMXは除外されており、その機能はまだ定義されていません(将来の使用のために予約されています)。

cr0 レジスタには、実行中の特定のタスクに関係なく、マイクロプロセッサの動作モードを制御し、その状態をグローバルに反映するシステム フラグが含まれています。

システム フラグの目的:

1) pe (プロテクト イネーブル)、ビット 0 - 動作の保護モードを有効にします。 このフラグの状態は、0 つのモード (リアル (pe = 1) または保護 (pe = XNUMX)) のどちらでマイクロプロセッサが特定の時間に動作しているかを示します。

2) mp (Math Present)、ビット 1 - コプロセッサーの存在。 常に 1。

3) ts (タスク切り替え)、ビット 3 - タスク切り替え。 プロセッサは、別のタスクに切り替えると、このビットを自動的に設定します。

4)am(アライメントマスク)、ビット18-アライメントマスク。 このビットは、アライメント制御を有効(am = 1)または無効(am = 0)にします。

5) cd (キャッシュ無効)、ビット 30 - キャッシュ メモリを無効にします。

このビットを使用して、内部キャッシュ(第1レベルのキャッシュ)の使用を無効(cd = 0)または有効(cd = XNUMX)にすることができます。

6)pg(PaGing)、ビット31-ページングを有効(pg = 1)または無効(pg = 0)にします。

このフラグは、メモリ編成のページングモデルで使用されます。

cr2 レジスターは RAM ページングで使用され、現在の命令が現在メモリー内にないメモリー・ページに含まれるアドレスにアクセスしたときの状況を登録します。

このような状況では、マイクロプロセッサで例外番号 14 が発生し、この例外を引き起こした命令のリニア 32 ビット アドレスがレジスタ cr2 に書き込まれます。 この情報により、例外ハンドラ14は、所望のページを決定し、それをメモリにスワップし、プログラムの通常の動作を再開する。

cr3レジスタは、メモリのページングにも使用されます。 これは、いわゆる第20レベルのページディレクトリレジスタです。 これには、現在のタスクのページディレクトリの1024ビットの物理ベースアドレスが含まれています。 このディレクトリには、32個の1024ビット記述子が含まれています。各記述子には、第32レベルのページテーブルのアドレスが含まれています。 次に、各第4レベルのページテーブルには、メモリ内のページフレームをアドレス指定するXNUMX個のXNUMXビット記述子が含まれています。 ページフレームサイズはXNUMXKBです。

3. システムアドレスのレジスタ

これらのレジスタは、メモリ管理レジスタとも呼ばれます。

これらは、マイクロプロセッサのマルチタスク モードでプログラムとデータを保護するように設計されています。 マイクロプロセッサ保護モードで動作している場合、アドレス空間は次のように分割されます。

1) グローバル - すべてのタスクに共通。

2) ローカル - タスクごとに個別。

この分離により、マイクロプロセッサ アーキテクチャに次のシステム レジスタが存在することが説明されます。

1) グローバル記述子テーブル gdtr (Global Descriptor Table Register) のレジスタ。サイズは 48 ビットで、グローバル記述子テーブル GDT の 32 ビット (ビット 16 ~ 47) のベースアドレスと 16 ビット (ビット0-15) GDT テーブルのバイト単位のサイズである制限値。

2) ローカル記述子テーブル レジスタ ldtr (ローカル記述子テーブル レジスタ)。サイズは 16 ビットで、ローカル記述子テーブル LDT の記述子のいわゆるセレクターを含みます。ローカル記述子テーブル LDT を含むセグメント。

3)48ビットのサイズを持ち、IDT割り込み記述子テーブルの32ビット(ビット16-47)のベースアドレスと16ビット(ビット)を含む割り込み記述子テーブルidtr(割り込み記述子テーブルレジスタ)のレジスタ0-15)IDTテーブルのバイト単位のサイズである制限値。

4) 16 ビット タスク レジスタ tr (タスク レジスタ) には、ldtr レジスタと同様に、セレクター、つまり GDT テーブル内の記述子へのポインターが含まれます. この記述子は、現在のタスク セグメント ステータス (TSS) を記述します。 このセグメントは、システム内のタスクごとに作成され、厳密に規制された構造を持ち、タスクのコンテキスト (現在の状態) を含みます。 TSS セグメントの主な目的は、別のタスクに切り替える瞬間のタスクの現在の状態を保存することです。

4.デバッグレジスタ

これは、ハードウェアのデバッグを目的とした非常に興味深いレジスタのグループです。 ハードウェアデバッグツールは、i486マイクロプロセッサで最初に登場しました。 ハードウェアでは、マイクロプロセッサにはXNUMXつのデバッグレジスタが含まれていますが、実際に使用されるのはそのうちのXNUMXつだけです。

レジスタ dr0、dr1、dr2、dr3 は 32 ビット幅を持ち、0 つのブレークポイントのリニア アドレスを設定するように設計されています。この場合に使用されるメカニズムは次のとおりです。現在のプログラムによって生成されたアドレスがレジスタ dr3 ~ dr1 のアドレスと比較され、一致する場合は番号 XNUMX のデバッグ例外が生成されます。

レジスタ dr6 は、デバッグ ステータス レジスタと呼ばれます。 このレジスタのビットは、最後の例外番号 1 が発生した理由に従って設定されます。

これらのビットとその目的をリストします。

1) b0 - このビットが 1 に設定されている場合、最後の例外 (割り込み) は、レジスタ dr0 で定義されたチェックポイントに到達した結果として発生しました。

2)b1-b0と同様ですが、レジスタdr1のチェックポイント用です。

3)b2-b0と同様ですが、レジスタdr2のチェックポイント用です。

4) bЗ - b0 に似ていますが、レジスタ dr3 のチェックポイント用です。

5) bd (ビット 13) - デバッグ レジスタを保護します。

6) bs (ビット 14) - eflags レジスタのフラグ tf = 1 の状態によって例外 1 が発生した場合、1 に設定されます。

7) bt (ビット 15) は、例外 1 が TSS t = 1 に設定されたトラップ ビットを持つタスクへの切り替えによって発生した場合に 1 に設定されます。

このレジスタの他のすべてのビットはゼロで埋められます。 例外ハンドラ 1 は、dr6 の内容に基づいて、例外の理由を判断し、必要なアクションを実行する必要があります。

レジスタ dr7 は、デバッグ制御レジスタと呼ばれます。 これには、割り込みを生成する次の条件を指定できる XNUMX つのデバッグ ブレークポイント レジスタのそれぞれのフィールドが含まれています。

1)チェックポイント登録場所-現在のタスクまたは任意のタスクのみ。 これらのビットは、レジスタdr8の下位7ビットを占有します(レジスタdr2、dr0、dr1、dr2によってそれぞれ設定されたブレークポイント(実際にはブレークポイント)ごとに3ビット)。

各ペアの最初のビットは、いわゆるローカル解像度です。 設定すると、現在のタスクのアドレス空間内にある場合にブレークポイントが有効になります。

各ペアの XNUMX 番目のビットは、グローバル許可を定義します。これは、指定されたブレークポイントが、システムに存在するすべてのタスクのアドレス空間内で有効であることを示します。

2) 割り込みが開始されるアクセスのタイプ: コマンドのフェッチ時、書き込み時、またはデータの書き込み/読み取り時のみ。 割り込みの発生のこの性質を決定するビットは、このレジスタの上部に配置されています。 システム レジスタのほとんどは、プログラムでアクセスできます。

LECTURE No. 16. アセンブラプログラム

1. アセンブラでのプログラムの構造

アセンブリ言語プログラムは、メモリ セグメントと呼ばれるメモリ ブロックの集まりです。 プログラムは、これらのブロックセグメントの XNUMX つまたは複数で構成されます。 各セグメントには言語センテンスのコレクションが含まれており、各センテンスはプログラム コードの個別の行を占めています。

アセンブリ ステートメントには、次の XNUMX つのタイプがあります。

1) 機械コマンドのシンボリックな類似物であるコマンドまたは命令。 変換プロセス中に、アセンブリ命令はマイクロプロセッサ命令セットの対応するコマンドに変換されます。

2)マクロ。 これらは、特定の方法で形式化され、放送中に他の文に置き換えられるプログラムのテキストの文です。

3) ディレクティブ。アセンブラ トランスレータに特定のアクションを実行するよう指示します。 ディレクティブには、マシン表現に対応するものはありません。

4)ロシア語のアルファベットの文字を含む任意の文字を含むコメント行。 コメントは翻訳者によって無視されます。

2.アセンブリ構文

プログラムを構成する文は、コマンド、マクロ、ディレクティブ、またはコメントに対応する構文構造である場合があります。 アセンブラ トランスレータがそれらを認識できるようにするには、特定の構文規則に従って作成する必要があります。 これを行うには、文法規則のような、言語の構文の正式な記述を使用するのが最善です。 このようにプログラミング言語を記述する最も一般的な方法は、構文図と拡張 Backus-Naur 形式です。 実際に使用する場合は、構文図の方が便利です。 たとえば、アセンブリ言語ステートメントの構文は、次の図に示す構文図を使用して記述できます。

米。 4. アセンブラ文の形式

米。 5.指令フォーマット

米。 6. コマンドとマクロのフォーマット

これらの図面について:

1)ラベル名-識別子。その値は、それが示すプログラムのソースコードの文の最初のバイトのアドレスです。

2) name - このディレクティブを同じ名前の他のディレクティブと区別する識別子。 アセンブラが特定のディレクティブを処理した結果、この名前に特定の特性を割り当てることができます。

3)オペコード(COP)およびディレクティブは、対応するマシン命令、マクロ命令、またはトランスレータディレクティブのニーモニック指定です。

4)オペランド-コマンド、マクロ、またはアセンブラディレクティブの一部であり、操作が実行されるオブジェクトを示します。 アセンブラオペランドは、数値定数とテキスト定数、変数ラベル、および演算子記号といくつかの予約語を使用した識別子を使用した式で記述されます。

シンタックスダイアグラムの使い方は? 非常に簡単です。必要なのは、ダイアグラムの入力(左)から出力(右)までのパスを見つけてそれをたどるだけです。 そのようなパスが存在する場合、文または構文は構文的に正しいです。 そのようなパスがない場合、コンパイラはこの構造を受け入れません。 シンタックスダイアグラムを操作するときは、矢印で示されたトラバーサルの方向に注意してください。パスの中には、右から左にたどることができるパスがある場合があります。 実際、構文図は、プログラムの入力文を解析するときの翻訳者の論理を反映しています。

プログラムのテキストを記述するときに使用できる文字は次のとおりです。

1) すべてのラテン文字: A - Z、a - z。 この場合、大文字と小文字は同等と見なされます。

2) 0 から 9 までの数字。

3)記号?、@、S、_、&;

4)セパレーター。

アセンブラ文は語彙素から形成されます。語彙素は、翻訳者にとって意味のある有効な言語記号の構文的に分離不可能なシーケンスです。

トークンは次のとおりです。

1.識別子は、操作コード、変数名、ラベル名などのプログラムオブジェクトを指定するために使用される有効な文字のシーケンスです。 識別子の記述規則は次のとおりです。識別子は255つ以上の文字で構成できます。 文字として、ラテンアルファベットの文字、数字、およびいくつかの特殊文字(_、?、$、@)を使用できます。 識別子を数字で始めることはできません。 識別子の長さは最大32文字ですが、トランスレータは最初のXNUMX文字のみを受け入れ、残りは無視します。 mvコマンドラインオプションを使用して、可能な識別子の長さを調整できます。 さらに、大文字と小文字を区別するか、それらの違いを無視するように翻訳者に指示することができます(これはデフォルトで行われます)。 これには、/ mu、/ ml、/mxコマンドラインオプションが使用されます。

2. 文字の連鎖 - 一重引用符または二重引用符で囲まれた一連の文字。

3. XNUMX 進数、XNUMX 進数、XNUMX 進数のいずれかの整数。 アセンブラープログラムで数字を書き込むときの数字の識別は、特定の規則に従って実行されます。

1)25進数では、139やXNUMXなどの追加の文字を識別する必要はありません。

2)プログラムのソーステキストで10010101進数を識別するには、ゼロとそれらを構成するXNUMXを書き込んだ後にラテン語の「b」を付ける必要があります(例:XNUMX b)。

3)XNUMX進数には、次のように書くときに、より多くの規則があります。

a) まず、数字 0 ~ 9、ラテンアルファベットの小文字と大文字、a、b、c、d、e、Gili D B、C、D、E、E で構成されます。

b) 次に、0 進数は 9 ~ 190845 の数字のみで構成されたり (たとえば、 5)、ラテン文字の文字で始まることがある (たとえば、 efl0 ) ため、翻訳者は 5 進数を認識するのが難しい場合があります。特定のトークンが XNUMX 進数でも識別子でもないことを翻訳者に「説明」するには、プログラマは特別な方法で XNUMX 進数を強調表示する必要があります。これを行うには、XNUMX 進数を構成する一連の XNUMX 進数の末尾にラテン文字「h」を書き込みます。これは必須です。 XNUMX 進数が文字で始まる場合は、その前に先行ゼロが書き込まれます (XNUMX eflXNUMX h)。

このようにして、アセンブラプログラムの文がどのように構築されるかを理解しました。 しかし、これは最も表面的な見方にすぎません。

ほとんどすべての文には、何らかのアクションが実行される、またはその助けを借りて実行されるオブジェクトの説明が含まれています。 これらのオブジェクトはオペランドと呼ばれます。 オペランドは、命令またはディレクティブの影響を受けるオブジェクト (一部の値、レジスタ、またはメモリ セル) であるか、命令またはディレクティブのアクションを定義または改良するオブジェクトです。

オペランドは、算術演算子、論理演算子、ビット演算子、および属性演算子と組み合わせて、値を計算したり、特定のコマンドまたはディレクティブの影響を受けるメモリ位置を決定したりできます。

次の分類のオペランドの特性をより詳細に検討してみましょう。

1)定数または即値オペランド-固定値を持つ数値、文字列、名前、または式。 名前は再配置可能であってはなりません。つまり、メモリにロードされるプログラムのアドレスに依存してはなりません。 たとえば、equalまたは=演算子を使用して定義できます。

2) アドレス オペランド。アドレスの 7 つのコンポーネントであるセグメントとオフセットを指定することにより、メモリ内のオペランドの物理的な位置を指定します (図 XNUMX)。

米。 7. アドレスオペランドの記述構文

3) 再配置可能なオペランド - いくつかのメモリ アドレスを表す任意の記号名。 これらのアドレスは、一部の命令 (オペランドがラベルの場合) またはデータ (オペランドがデータ セグメント内のメモリ ロケーションの名前の場合) のメモリ ロケーションを示します。

再配置可能オペランドは、特定の物理メモリアドレスに関連付けられていないという点でアドレスオペランドとは異なります。 移動されるオペランドのアドレスのセグメントコンポーネントは不明であり、プログラムが実行のためにメモリにロードされた後に決定されます。

アドレス カウンターは特定の種類のオペランドです。 これは記号 S で示されます。このオペランドの特異性は、アセンブラー トランスレーターがソース プログラムでこのシンボルを検出すると、代わりにアドレス カウンターの現在の値を代用することです。 アドレス カウンター、または配置カウンターと呼ばれることもある値は、コード セグメントの先頭からの現在のマシン命令のオフセットです。 リスト形式では、XNUMX 列目または XNUMX 列目がアドレス カウンターに対応します (ネスト レベルの列がリストに存在するかどうかによって異なります)。 例としてリストを取り上げると、トランスレータが次のアセンブラ命令を処理するときに、生成されたマシン命令の長さだけアドレス カウンタが増加することは明らかです。 この点を正しく理解することが重要です。 たとえば、アセンブラー ディレクティブを処理しても、カウンターは変更されません。 ディレクティブは、アセンブラー コマンドとは異なり、プログラムのマシン表現を形成するために特定のアクションを実行するためのコンパイラーへの指示にすぎず、コンパイラーはメモリ内に構造を生成しません。

このような式を使用してジャンプする場合は、この式が使用されている命令自体の長さに注意してください。アドレス カウンタの値は、この命令の命令セグメント内のオフセットに対応し、それに続く命令には対応しないためです。 . この例では、jmp コマンドは 2 バイトを使用します。 ただし、命令の長さは、使用するオペランドによって異なります。 レジスタ オペランドを持つ命令は、オペランドの XNUMX つがメモリにある命令よりも短くなります。 ほとんどの場合、この情報は、マシン命令の形式を知り、命令のオブジェクト コードを使用してリスト列を分析することによって取得できます。

4) register オペランドは単なるレジスタ名です。 アセンブラ プログラムでは、すべての汎用レジスタとほとんどのシステム レジスタの名前を使用できます。

5)ベースオペランドとインデックスオペランド。 このオペランドタイプは、間接ベース、間接インデックスアドレス指定、またはそれらの組み合わせと拡張を実装するために使用されます。

6) 構造オペランドは、構造と呼ばれる複雑なデータ型の特定の要素にアクセスするために使用されます。

レコード(構造体タイプと同様)は、あるレコードのビットフィールドにアクセスするために使用されます。

オペランドは、機械命令の一部を形成する基本コンポーネントであり、操作が実行されるオブジェクトを示します。 より一般的なケースでは、オペランドは、式と呼ばれるより複雑な形式のコンポーネントとして含めることができます。 式は、全体として考慮されるオペランドと演算子の組み合わせです。 式の評価の結果は、メモリセルのアドレスまたは定数(絶対)値になります。

オペランドの可能なタイプについてはすでに検討しました。 ここで、アセンブラー演算子の可能なタイプと、アセンブラー式を形成するための構文規則をリストし、演算子について簡単に説明します。

1. 算術演算子。 これらには以下が含まれます:

1) 単項 "+" および "-";

2) バイナリ "+" および "-";

3) 乗算 "*";

4) 整数除算 "/";

5)除算「mod」から剰余を取得します。

これらの演算子は、表6,7,8の優先レベル4、XNUMX、XNUMXにあります。

米。 8. 算術演算の構文

2.シフト演算子は、指定されたビット数だけ式をシフトします(図9)。

米。 9.シフト演算子の構文

3. 比較演算子 (値「true」または「false」を返す) は、論理式の形成を目的としています (図 10 および表 3)。 論理値「true」はデジタル単位に対応し、「false」はゼロに対応します。

米。 10. 比較演算子の構文

表3.比較演算子

4. 論理演算子は、式に対してビット演算を実行します (図 11)。 式は絶対的でなければなりません。つまり、その数値は翻訳者が計算できるものでなければなりません。

米。 11.論理演算子の構文

5. インデックス演算子 []。 括弧も演算子であり、翻訳者は括弧の存在を、これらの括弧の後ろに式_1の値を追加し、括弧で囲まれた式_2を追加する命令として認識します(図12)。

米。 12. インデックス演算子の構文

アセンブラに関する文献では、次の指定が採用されていることに注意してください。テキストがレジスタの内容を参照する場合、その名前は括弧内に取られます。 こちらの表記も踏襲いたします。

6. ptr型再定義演算子は、式で定義されたラベルまたは変数の型を再定義または修飾するために使用されます(図13)。

この型は、次のいずれかの値を取ることができます: byte、word、dword、qword、tbyte、near、far。

米。 13. 型再定義演算子の構文

7. セグメント再定義演算子 ":" (コロン) は、具体的に指定されたセグメント コンポーネント ("セグメント レジスタ名"、対応する SEGMENT ディレクティブの "セグメント名"、または "グループ名") に関連する物理アドレスの計算を強制します (図. 14)。 セグメンテーションについて説明したとき、マイクロプロセッサがハードウェア レベルで 16 種類のセグメント (コード、スタック、データ) をサポートしているという事実について話しました。 このハードウェア サポートとは何ですか? たとえば、次のコマンドの実行を選択するには、マイクロプロセッサは必ずセグメント レジスタ cs の内容だけを確認する必要があります。 そして、このレジスタには、ご存知のように、命令セグメントの先頭の (まだシフトされていない) 物理アドレスが含まれています。 特定の命令のアドレスを取得するには、マイクロプロセッサは cs の内容を 20 倍し (16 ビットのシフトを意味します)、結果の XNUMX ビット値を ip レジスタの XNUMX ビット内容に加算する必要があります。 マイクロプロセッサが機械語命令のオペランドを処理するときも、ほぼ同じことが起こります。 オペランドがアドレス (物理アドレスの一部にすぎない実効アドレス) であることがわかった場合、それを検索するセグメントを認識します。デフォルトでは、開始アドレスがセグメント レジスタ ds に格納されているセグメントです。 .

しかし、スタック セグメントはどうでしょうか。 今回の考察では、sp レジスタと bp レジスタに関心があります。 マイクロプロセッサがこれらのレジスタの XNUMX つをオペランド (またはオペランドが式の場合はその一部) として認識した場合、デフォルトでは、セグメント コンポーネントとして ss レジスタの内容を使用して、オペランドの物理アドレスを形成します。 これは、マイクロプログラム制御ユニット内の一連のマイクロプログラムであり、それぞれがマイクロプロセッサ機械語命令システム内の命令の XNUMX つを実行します。 各マイクロプログラムは、独自のアルゴリズムに従って動作します。 もちろん、変更することはできませんが、少し修正することはできます。 これは、オプションのマシン コマンド プレフィックス フィールドを使用して行われます。 コマンドの動作に同意する場合、このフィールドはありません。 コマンドのアルゴリズムに修正を加えたい場合 (もちろん、特定のコマンドで許容される場合)、適切なプレフィックスを形成する必要があります。

プレフィックスは、数値がその目的を決定する XNUMX バイトの値です。 マイクロプロセッサは、指定された値によってこのバイトがプレフィックスであることを認識し、受信した命令を考慮してマイクロプログラムのさらなる作業が実行され、その作業が修正されます。 ここで、そのうちの XNUMX つであるセグメント置換 (再定義) プレフィックスに関心があります。 その目的は、デフォルト セグメントを使用したくないことをマイクロプロセッサ (および実際にはファームウェア) に示すことです。 もちろん、このような再定義の可能性は限られています。 コマンド セグメントは再定義できません。次の実行可能なコマンドのアドレスは、cs: ip ペアによって一意に決定されます。 そして、ここでスタックとデータのセグメント - それは可能です。 それが「:」演算子の目的です。 このステートメントを処理するアセンブラー・トランスレーターは、対応する XNUMX バイトのセグメント置換接頭部を生成します。

米。 14. セグメント再定義演算子の構文

8. 構造体型の命名演算子「.」(ドット) も、式の中にある場合、コンパイラに特定の計算を強制的に実行させます。

9. 式 seg のアドレスのセグメント コンポーネントを取得する演算子は、式のセグメントの物理アドレスを返します (図 15)。これは、ラベル、変数、セグメント名、グループ名、または何らかの記号名にすることができます。 .

米。 15. セグメント構成要素受信演算子の構文

10. 式オフセットのオフセットを取得する演算子を使用すると、式が定義されているセグメントの先頭からのバイト単位で、式のオフセットの値を取得できます (図 16)。

米。 16. オフセット取得演算子の構文

高級言語と同様に、式を評価する際のアセンブラ演算子の実行は、優先順位に従って実行されます (表 4)。 同じ優先度の操作は、左から順に実行されます。 優先順位の高い括弧を配置することで、実行順序を変更できます。

表 4. 演算子とその優先順位

3.セグメンテーションディレクティブ

前の説明の過程で、アセンブリ言語プログラムで命令とオペランドを記述するためのすべての基本的な規則を見つけました。 トランスレータがコマンドを処理し、マイクロプロセッサがコマンドを実行できるように、コマンドのシーケンスを適切にフォーマットする方法の問題は未解決のままです。

マイクロプロセッサのアーキテクチャを検討すると、XNUMXつのセグメントレジスタがあり、それを介して同時に動作できることがわかりました。

1)XNUMXつのコードセグメント。

2)XNUMXつのスタックセグメントを使用。

3)XNUMXつのデータセグメント。

4)XNUMXつの追加データセグメントを使用。

セグメントは、対応するセグメントレジスタの値に関連してアドレスが計算されるコマンドおよび(または)データによって占有される物理的なメモリ領域であることを再度思い出してください。

アセンブラのセグメントの構文上の説明は、図17に示す構造です。

米。 17.セグメント記述構文

セグメントの機能は、プログラムをコード、データ、およびスタックのブロックに単純に分割するよりもいくらか広いことに注意することが重要です。 セグメンテーションは、モジュラープログラミングの概念に関連するより一般的なメカニズムの一部です。 これには、さまざまなプログラミング言語のオブジェクトモジュールを含む、コンパイラによって作成されたオブジェクトモジュールの設計の統合が含まれます。 これにより、異なる言語で書かれたプログラムを組み合わせることができます。 SEGMENTディレクティブのオペランドが意図されているのは、このようなユニオンのさまざまなオプションを実装するためです。

それらをより詳細に検討してください。

1.セグメントアラインメント属性(アラインメントタイプ)は、セグメントの先頭が指定された境界に配置されるようにリンカに指示します。 適切な位置合わせにより、i80x86プロセッサでのデータアクセスが高速になるため、これは重要です。 この属性の有効な値は次のとおりです:

1) BYTE - アライメントは実行されません。 セグメントは任意のメモリ アドレスから開始できます。

2) WORD - セグメントは 0 の倍数のアドレスで開始します。つまり、物理アドレスの最後の (最下位) ビットは XNUMX (ワード境界に整列) です。

3) DWORD - セグメントは 0 の倍数のアドレスで開始します。つまり、最後の XNUMX (最下位) ビットは XNUMX (ダブルワード境界整列) です。

4)PARA-セグメントは16の倍数のアドレスで始まります。つまり、アドレスの最後のXNUMX進数はOh(段落の境界に揃える)でなければなりません。

5)PAGE-セグメントは256の倍数のアドレスで始まります。つまり、最後の00桁の256進数はXNUMXh(XNUMXバイトのページの境界に揃えられている)でなければなりません。

6)MEMPAGE-セグメントは4 KBの倍数のアドレスで始まります。つまり、最後の4桁のXNUMX進数はOOOh(次のXNUMX KBのメモリページのアドレス)である必要があります。 デフォルトの配置タイプはPARAです。

2.結合セグメント属性(組み合わせタイプ)は、同じ名前を持つ異なるモジュールのセグメントを結合する方法をリンカーに指示します。 セグメントの組み合わせ属性値は次のようになります:

1)PRIVATE-セグメントは、このモジュールの外部で同じ名前の他のセグメントとマージされません。

2)PUBLIC-リンカーに同じ名前のすべてのセグメントを接続させます。 新たに統合されたセグメントは、完全かつ継続的になります。 オブジェクトのすべてのアドレス(オフセット)は、コマンドとデータセグメントのタイプによって異なる場合があり、この新しいセグメントの先頭を基準にして計算されます。

3) COMMON - 同じ名前のすべてのセグメントを同じアドレスに配置します。 指定された名前のすべてのセグメントがオーバーラップし、メモリを共有します。 結果のセグメントのサイズは、最大のセグメントのサイズと等しくなります。

4)AT xxxx-段落の絶対アドレスにセグメントを配置します(段落はメモリの量であり、16の倍数であるため、段落アドレスの最後の0進数はXNUMXです)。 段落の絶対アドレスはxxxで指定されます。 リンカは、結合属性を考慮して、セグメントを特定のメモリアドレスに配置します(これは、たとえば、ビデオメモリまたはROM>領域にアクセスするために使用できます)。 物理的には、これは、セグメントがメモリにロードされると、段落のこの絶対アドレスから開始して配置されることを意味しますが、それにアクセスするには、属性で指定された値を対応するセグメントレジスタにロードする必要があります。 このように定義されたセグメント内のすべてのラベルとアドレスは、指定された絶対アドレスを基準にしています。

5)STACK-スタックセグメントの定義。 リンカに同じ名前のすべてのセグメントを接続させ、ssレジスタを基準にしてこれらのセグメントのアドレスを計算します。 結合型STACK(スタック)は、ssレジスタがスタックセグメントの標準セグメントレジスタであることを除いて、結合型PUBLICに似ています。 spレジスタは、連結されたスタックセグメントの最後に設定されます。 スタックセグメントが指定されていない場合、リンカはスタックセグメントが見つからなかったという警告を発行します。 スタックセグメントが作成され、結合されたスタックタイプが使用されていない場合、プログラマはセグメントアドレスをssレジスタ(dsレジスタと同様)に明示的にロードする必要があります。

組み合わせ属性のデフォルトはPRIVATEです。

3.セグメントクラス属性(クラスタイプ)は、複数のモジュールセグメントからプログラムをアセンブルするときに、リンカが適切なセグメント順序を決定するのに役立つ引用符で囲まれた文字列です。 リンカは、同じクラス名を持つすべてのセグメントをメモリ内で結合します(クラス名は通常、何でもかまいませんが、セグメントの機能を反映している方がよいでしょう)。 クラス名の一般的な使用法は、プログラムのすべてのコードセグメントをグループ化することです(通常、これには「コード」クラスが使用されます)。 クラスタイピングメカニズムを使用して、初期化されたデータセグメントと初期化されていないデータセグメントをグループ化することもできます。

4.セグメントサイズ属性。 i80386以降のプロセッサの場合、セグメントは16ビットまたは32ビットにすることができます。 これは主に、セグメントのサイズと、セグメント内で物理アドレスが形成される順序に影響します。 属性は次の値を取ることができます。

1)USE16-これは、セグメントが16ビットアドレス指定を許可することを意味します。 物理アドレスを形成する場合、16ビットオフセットのみを使用できます。 したがって、このようなセグメントには、最大64KBのコードまたはデータを含めることができます。

2)USE32 - セグメントは 32 ビットになります。 物理アドレスを形成する場合、32 ビットのオフセットを使用できます。 したがって、このようなセグメントには最大 4 GB のコードまたはデータを含めることができます。

SEGMENTディレクティブとENDSディレクティブにはセグメントの機能目的に関する情報が含まれていないため、すべてのセグメントはそれ自体が同じです。 これらをコード、データ、またはスタックセグメントとして使用するには、事前にトランスレータに通知する必要があります。このことについては、図に示す形式の特別なASSUMEディレクティブが使用されます。 18.このディレクティブは、どのセグメントがどのセグメントレジスタにバインドされているかをトランスレータに通知します。 これにより、トランスレータはセグメントで定義されたシンボリック名を正しくバインドできます。 セグメントのセグメントレジスタへのバインドは、このディレクティブのオペランドを使用して実行されます。segment_nameは、SEGMENTディレクティブまたはnothingキーワードによってプログラムのソーステキストで定義されたセグメントの名前である必要があります。 キーワードnothingのみがオペランドとして使用されている場合、前のセグメントレジスタの割り当てはキャンセルされ、18つのセグメントレジスタすべてに対して一度にキャンセルされます。 ただし、セグメント名引数の代わりにキーワードnothingを使用できます。 この場合、名前がセグメント名のセグメントと対応するセグメントレジスタ間の接続が選択的に切断されます(図XNUMXを参照)。

米。 18. ASSUME ディレクティブ

コード、データ、およびスタックのXNUMXつのセグメントを含む単純なプログラムの場合、その説明を簡略化したいと思います。 これを行うために、トランスレータのMASMとTASMは、簡略化されたセグメンテーションディレクティブを使用する機能を導入しました。 しかし、ここでは、セグメントの配置と組み合わせを直接制御できないことを何らかの形で補償する必要があるという事実に関連する問題が発生しました。 これを行うために、簡略化されたセグメンテーションディレクティブとともに、MODELメモリモデルを指定するためのディレクティブの使用を開始しました。これにより、セグメントの配置を部分的に制御し、ASSUMEディレクティブの機能を実行し始めました(したがって、簡略化されたセグメンテーションディレクティブを使用する場合、 ASSUMEディレクティブは省略できます)。 このディレクティブは、セグメントをバインドします。セグメントは、簡略化されたセグメンテーションディレクティブを使用する場合、事前定義された名前を持ち、セグメントレジスタを使用します(ただし、dsを明示的に初期化する必要があります)。

MODEL ディレクティブの構文を図 19 に示します。

米。 19. MODEL ディレクティブの構文

MODEL ディレクティブの必須パラメータはメモリ モデルです。 このパラメータは、POU のメモリ セグメンテーション モデルを定義します。 プログラムモジュールは、前述の単純化されたセグメント記述ディレクティブによって定義される特定のタイプのセグメントのみを持つことができると想定されています。 これらのディレクティブを表 5 に示します。

表 5. 簡略化されたセグメント定義ディレクティブ

一部のディレクティブに [name] パラメータが存在することは、このタイプの複数のセグメントを定義できることを示しています。 一方、いくつかの種類のデータ セグメントが存在するのは、高水準言語の一部のコンパイラとの互換性を確保する必要があるためです。これらのコンパイラは、初期化されたデータと初期化されていないデータ、および定数に対して異なるデータ セグメントを作成します。

MODEL ディレクティブを使用する場合、トランスレータは、特定のメモリ モデルの特定の特性に関する情報を取得するために、プログラム操作中にアクセスできるいくつかの識別子を使用できるようにします (表 7)。 これらの識別子とその値をリストしましょう (表 6)。

表6.MODELディレクティブによって作成されたID

これで MODEL ディレクティブの説明を終了できます。 MODEL ディレクティブのオペランドは、プログラム セグメントのセット、データとコード セグメントのサイズ、およびセグメントとセグメント レジスタをリンクする方法を定義するメモリ モデルを指定するために使用されます。 表 7 は、MODEL ディレクティブの「メモリ モデル」パラメーターの値の一部を示しています。

表 7. メモリ モデル

MODEL ディレクティブの「修飾子」パラメーターを使用すると、選択したメモリ モデルを使用するいくつかの機能を指定できます (表 8)。

表8.メモリモデル修飾子

オプションのパラメータ "language" と "language modifier" は、プロシージャ コールのいくつかの機能を定義します。 これらのパラメーターを使用する必要性は、さまざまなプログラミング言語でプログラムを作成およびリンクするときに発生します。

ここで説明した標準および簡略化されたセグメンテーション ディレクティブは、相互に排他的ではありません。 標準ディレクティブは、プログラマがメモリ内のセグメントの配置および他のモジュールからのセグメントとの組み合わせを完全に制御したい場合に使用されます。

単純化されたディレクティブは、単純なプログラムや、高級言語で記述されたプログラム モジュールとのリンクを目的としたプログラムに役立ちます。 これにより、リンカーは、リンクと管理を標準化することで、異なる言語のモジュールを効率的にリンクできます。

LECTURE No. 17. アセンブラのコマンド構造

1. 機械命令構造

マシンコマンドは、特定の規則に従ってエンコードされたマイクロプロセッサへの指示であり、何らかの操作またはアクションを実行します。 各コマンドには、次を定義する要素が含まれています。

1) どうする? (この質問に対する答えは、オペレーション コード (COP) と呼ばれるコマンド要素によって与えられます)。

2) 何かを行う必要があるオブジェクト (これらの要素はオペランドと呼ばれます);

3) どうやって? (これらの要素はオペランド型と呼ばれ、通常は暗黙的に指定されます。)

図 20 に示す機械語命令の形式は、最も一般的なものです。 機械語命令の最大長は 15 バイトです。 実際のコマンドには、KOP のみの XNUMX つまで、はるかに少ない数のフィールドが含まれる場合があります。

米。 20. 機械命令フォーマット

機械語命令フィールドの目的を説明しましょう。

1. プレフィックス。

オプションの機械命令要素。各要素は1バイトであるか、省略できます。 メモリでは、プレフィックスがコマンドの前にあります。 プレフィックスの目的は、コマンドによって実行される操作を変更することです。 アプリケーションは、次のタイプのプレフィックスを使用できます。

1) セグメント置換プレフィックス。 スタックまたはデータをアドレス指定するために、この命令でどのセグメント レジスタを使用するかを明示的に指定します。 プレフィックスは、デフォルトのセグメント レジスタの選択を上書きします。 セグメント置換プレフィックスには、次の意味があります。

a)2eh-セグメントcsの置換。

b) 36h - セグメント ss の置換。

c)3eh-セグメントdsの置換。

d) 26h - セグメント es の置換。

e) 64h - セグメント fs の置換。

e) 65h - セグメント gs の置換。

2) アドレスのビット数プレフィックスは、アドレスのビット数 (32 ビットまたは 16 ビット) を指定します。 アドレス オペランドを使用する各命令には、そのオペランドのアドレスのビット幅が割り当てられます。 このアドレスは、16 ビットまたは 32 ビットにすることができます。 このコマンドのアドレス長が 16 ビットの場合、これはコマンドに 16 ビット オフセットが含まれていることを意味し (図 20)、セグメントの先頭に対するアドレス オペランドの 16 ビット オフセットに対応します。 図 21 のコンテキストでは、このオフセットは実効アドレスと呼ばれます。 アドレスが 32 ビットの場合、これは、コマンドに 32 ビット オフセットが含まれていることを意味し (図 20)、セグメントの先頭に対するアドレス オペランドの 32 ビット オフセットに対応し、その値は 32 を形成します。 -セグメント内のビット オフセット。 アドレス ビットネス プレフィックスを使用して、デフォルトのアドレス ビットネスを変更できます。 この変更は、プレフィックスが前にあるコマンドにのみ影響します。

米。 21.リアルモードでの物理アドレスの形成メカニズム

3) オペランド ビット幅プレフィックスは、アドレス ビット幅プレフィックスに似ていますが、命令が動作するオペランド ビット長 (32 ビットまたは 16 ビット) を示します。 デフォルトでアドレスおよびオペランドのビット幅属性を設定するためのルールは何ですか?

リアルモードと仮想18086モードでは、これらの属性の値は16ビットです。 プロテクトモードでは、属性値は実行可能セグメント記述子のDビットの状態に依存します。 D = 0の場合、デフォルトの属性値は16ビットです。 D = 1の場合、32ビット。

オペランド幅66h、アドレス幅67hのプレフィックス値。 リアル モード アドレス ビット プレフィックスを使用すると、32 ビット アドレッシングを使用できますが、64 KB のセグメント サイズ制限に注意してください。 アドレス幅プレフィックスと同様に、リアルモード オペランド幅プレフィックスを使用して、32 ビット オペランドを操作できます (たとえば、算術命令で)。

4) 繰り返しプレフィックスはチェーン コマンド (行処理コマンド) で使用されます。 このプレフィックスは、チェーンのすべての要素を処理するコマンドを「ループ」します。 コマンド システムは、次の XNUMX 種類のプレフィックスをサポートしています。

a)無条件(rep - OOh)、連鎖コマンドを特定の回数繰り返すことを強制します。

b) 条件付き (repe/repz - OOh、repne/repnz - 0f2h)。ループ時にいくつかのフラグをチェックし、チェックの結果、ループからの早期終了が可能です。

2. 操作コード。

コマンドによって実行される操作を説明する必須要素。 多くのコマンドはいくつかの操作コードに対応しており、それぞれが操作のニュアンスを決定します。 マシン命令の後続のフィールドは、操作に含まれるオペランドの位置とその使用の詳細を決定します。 これらのフィールドの考慮は、機械語命令でのオペランドの指定方法に関連するため、後で実行されます。

3. アドレッシング モード バイト modr/m。

このバイトの値は、使用されるオペランド アドレス形式を決定します。 オペランドは、メモリ内の 21 つまたは 20 つのレジスタに配置できます。 オペランドがメモリ内にある場合、modr/m バイトは、実効アドレスの計算に使用されるコンポーネント (オフセット、ベース、およびインデックス レジスタ) を指定します (図 XNUMX)。 プロテクト モードでは、sib バイト (Scale-Index-Base) を追加で使用して、メモリ内のオペランドの位置を特定できます。 modr/m バイトは XNUMX つのフィールドで構成されます (図 XNUMX)。

1) mod フィールドは、コマンドのオペランド アドレスが占めるバイト数を決定します (図 20、コマンドのオフセット フィールド)。 mod フィールドは、「命令オフセット」オペランドのアドレスを変更する方法を指定する r/m フィールドと組み合わせて使用​​されます。 たとえば、mod = 00 の場合、これはコマンドにオフセット フィールドがなく、オペランド アドレスがベースおよび (または) インデックス レジスタの内容によって決定されることを意味します。 実効アドレスの計算に使用されるレジスタは、このバイトの値によって決まります。 mod = 01 の場合、これはオフセット フィールドがコマンドに存在し、1 バイトを占有し、ベースおよび (または) インデックス レジスタの内容によって変更されることを意味します。 mod = 10 の場合、オフセット フィールドがコマンドに存在し、2 または 4 バイト (デフォルトまたはプレフィックスで定義されたアドレス サイズに応じて) を占有し、ベースおよび/またはインデックス レジスタの内容によって変更されることを意味します。 mod = 11 の場合、これはメモリにオペランドがないことを意味します。オペランドはレジスタにあります。 命令で即値オペランドが使用される場合、mod バイトの同じ値が使用されます。

2) reg/cop フィールドは、最初のオペランドの代わりにコマンドに配置されたレジスタ、またはオペコードの可能な拡張のいずれかを決定します。

3) r/m フィールドは、mod フィールドと組み合わせて使用​​され、コマンド内の最初のオペランドの場所にあるレジスタ (mod = 11 の場合)、または実効アドレスの計算に使用されるベースおよびインデックス レジスタのいずれかを決定します。 (コマンドのオフセット フィールドと一緒に)。

4.バイトスケール-インデックス-ベース(バイトシブ)。

オペランドのアドレス指定の可能性を広げるために使用されます。 マシン命令での sib バイトの存在は、mod フィールドの値 01 または 10 と r/m = 100 フィールドの値の組み合わせによって示されます. sib バイトは XNUMX つのフィールドで構成されます:

1) スケール フィールド ss。 このフィールドには、sib バイトの次の 3 ビットを占めるインデックス コンポーネント インデックスのスケーリング ファクタが含まれます。 ss フィールドには、1、2、4、8 のいずれかの値を含めることができます。

実効アドレスを計算するとき、インデックス レジスタの内容はこの値で乗算されます。

2) インデックス フィールド。 オペランドの実効アドレスの計算に使用されるインデックス レジスタ番号を格納するために使用されます。

3)ベースフィールド。 ベースレジスタ番号を格納するために使用されます。これは、オペランドの実効アドレスの計算にも使用されます。 ほとんどすべての汎用レジスタは、ベースレジスタおよびインデックスレジスタとして使用できます。

5.コマンドのオフセットフィールド。

オペランドの実効アドレスの値の全部または一部(上記の考慮事項に従う)を表す8、16、または32ビットの符号付き整数。

6. 即値オペランドのフィールド。

8 ビット、16 ビット、または 32 ビットの即値オペランドであるオプション フィールド。 もちろん、このフィールドの存在は modr/m バイトの値に反映されます。

2. 命令オペランドの指定方法

オペランドはファームウェアレベルで暗黙的に設定されます

この場合、命令には明示的にオペランドが含まれていません。 コマンド実行アルゴリズムは、いくつかのデフォルトオブジェクト(レジスタ、eflagsのフラグなど)を使用します。

たとえば、cli コマンドと sti コマンドは暗黙的に eflags レジスターの if interrupt フラグを処理し、xlat コマンドは暗黙的に al レジスターと、ds:bx レジスター ペアで指定されたアドレスにあるメモリ内の行にアクセスします。

命令自体にオペランドを指定する(即値オペランド)

オペランドは命令コード内にあります。つまり、オペランドはその一部です。 このようなオペランドをコマンドに格納するために、最大32ビット長のフィールドが割り当てられます(図20)。 即値オペランドは、XNUMX番目(ソース)のオペランドのみにすることができます。 デスティネーションオペランドは、メモリまたはレジスタのいずれかにあります。

例: mov ax,0ffffti は、2 進定数 ffff をレジスタ ax に移動します。 add sum, 2 コマンドは、アドレス sum のフィールドの内容に整数 XNUMX を加算し、結果を最初のオペランドの場所、つまりメモリに書き込みます。

オペランドはいずれかのレジスタにあります

レジスタオペランドはレジスタ名で指定します。 レジスタは次のように使用できます。

1)32ビットレジスタEAX、EBX、ECX、EDX、ESI、EDI、ESP、EUR;

2)16ビットレジスタAX、BX、CX、DX、SI、DI、SP、BP;

3) 8 ビットレジスタ AH、AL、BH、BL、CH、CL、DH、DL。

4)セグメントレジスタCS、DS、SS、ES、FS、GS。

たとえば、add ax,bx 命令は、レジスタ ax と bx の内容を加算し、結果を bx に書き込みます。 dec si コマンドは、si の内容を 1 減らします。

オペランドはメモリ内にあります

これは最も複雑であると同時に、オペランドを指定する最も柔軟な方法です。 これにより、次の XNUMX つの主なタイプのアドレス指定を実装できます: 直接および間接。

次に、間接アドレッシングには次の種類があります。

1) 間接ベースアドレッシング。 他の名前はレジスタ間接アドレッシングです。

2)オフセットを使用した間接ベースアドレス指定。

3)オフセットを使用した間接インデックスアドレス指定。

4)間接ベースインデックスアドレス指定。

5)オフセットを使用した間接ベースインデックスアドレス指定。

オペランドはI/Oポートです

マイクロプロセッサは、RAMアドレス空間に加えて、I/Oデバイスへのアクセスに使用されるI/Oアドレス空間を維持します。 I/Oアドレス空間は64KBです。 アドレスは、このスペース内の任意のコンピューターデバイスに割り当てられます。 このスペース内の特定のアドレス値は、I/Oポートと呼ばれます。 物理的には、I / Oポートはハードウェアレジスタ(マイクロプロセッサレジスタと混同しないでください)に対応し、特別なアセンブラ命令を使用してアクセスされます。

たとえば、次のように

al、60h; ポート60hからバイトを入力します

I/O ポートによってアドレス指定されるレジスタは、8,16、32、または XNUMX ビット幅にすることができますが、レジスタのビット幅は特定のポートに対して固定されています。 in コマンドと out コマンドは、一定範囲のオブジェクトに対して機能します。 いわゆるアキュムレータ レジスタ EAX、AX、AL は、情報源または受信者として使用されます。 レジスタの選択は、ポートのビット数によって決まります。 ポート番号は、in および out 命令の即値オペランドとして、または DX レジスタの値として指定できます。 最後の方法では、プログラムでポート番号を動的に決定できます。

オペランドはスタックにあります

命令にはオペランドがまったくない場合もあれば、XNUMX つまたは XNUMX つのオペランドがある場合もあります。 ほとんどの命令は XNUMX つのオペランドを必要とし、そのうちの XNUMX つはソース オペランドで、もう XNUMX つはデスティネーション オペランドです。 XNUMX つのオペランドをレジスタまたはメモリに配置できることが重要であり、XNUMX 番目のオペランドはレジスタまたは直接命令に配置する必要があります。 即値オペランドは、ソース オペランドのみにすることができます。 XNUMX オペランドの機械語命令では、次のオペランドの組み合わせが可能です。

1) 登録 - 登録;

2)レジスタ-メモリ;

3)メモリ-レジスタ;

4)即値オペランド-レジスタ;

5) 即値オペランド - メモリ。

この規則には、次の点について例外があります。

1)メモリからメモリにデータを移動できるチェーンコマンド。

2)メモリからメモリ内にあるスタックにデータを転送できるスタックコマンド。

3) コマンドで指定されたオペランドに加えて、XNUMX 番目の暗黙のオペランドも使用する乗算タイプのコマンド。

リストされているオペランドの組み合わせのうち、レジスタ - メモリとメモリ - レジスタが最もよく使用されます。 それらの重要性を考慮して、それらをより詳細に検討します。 アセンブラ コマンドの例を使用して説明を行います。アセンブラ コマンドの形式が、21 つまたは別のタイプのアドレッシングが適用されたときにどのように変化するかを示します。 この点に関して、図 4 をもう一度見てください。これは、マイクロプロセッサのアドレス バス上に物理アドレスを形成する原理を示しています。 オペランドのアドレスは、16 ビット分シフトされたセグメント レジスタの内容と XNUMX ビットの実効アドレスの XNUMX つのコンポーネントの合計として形成されることがわかります。オフセットとインデックス。

3.アドレス指定方法

メモリ内のアドレス指定オペランドの主なタイプの機能をリストして検討します。

1) 直接アドレッシング;

2) 間接基本 (レジスタ) アドレッシング。

3)オフセットを使用した間接的な基本(レジスタ)アドレス指定。

4)オフセットを使用した間接インデックスアドレス指定。

5)間接ベースインデックスアドレス指定。

6)オフセットを使用した間接ベースインデックスアドレス指定。

直接アドレッシング

これは、メモリ内のオペランドをアドレス指定する最も単純な形式です。これは、実効アドレスが命令自体に含まれており、それを形成するために追加のソースやレジスタが使用されていないためです。 実効アドレスは、マシン命令オフセット フィールド (図 20 を参照) から直接取得され、サイズは 8、16、32 ビットです。 この値は、データ セグメントにあるバイト、ワード、またはダブル ワードを一意に識別します。

直接アドレッシングには XNUMX つのタイプがあります。

相対的な直接アドレス指定

相対ジャンプアドレスを示す条件付きジャンプ命令に使用されます。 このような遷移の相対性は、マシン命令のオフセットフィールドに8、16、または32ビットの値が含まれているという事実にあります。これらの値は、命令の操作の結果として、の内容に追加されます。 ip/eip命令ポインタレジスタ。 この追加の結果として、遷移が実行されるアドレスが取得されます。

絶対直接アドレス指定

この場合、実効アドレスは機械命令の一部ですが、このアドレスは命令のオフセットフィールドの値からのみ形成されます。 メモリ内のオペランドの物理アドレスを形成するために、マイクロプロセッサはこのフィールドにセグメントレジスタの値を4ビットシフトして追加します。 このアドレス指定のいくつかの形式は、アセンブラ命令で使用できます。

しかし、このようなアドレス指定はめったに使用されません。プログラムで一般的に使用されるセルには、記号名が割り当てられます。 変換中、アセンブラーはこれらの名前のオフセット値を計算し、「命令オフセット」フィールドで生成するマシン命令に置き換えます。 その結果、マシン命令はそのオペランドを直接アドレス指定し、実際にはそのフィールドの XNUMX つに実効アドレスの値を持っていることがわかります。

他のタイプのアドレッシングは間接的です。 これらのタイプのアドレス指定の名前にある「間接」という言葉は、実効アドレスの一部のみが命令自体にあり、残りのコンポーネントはレジスターにあることを意味します。レジスターは modr/m バイトと、おそらく、兄弟バイトによって。

間接基本 (レジスタ) アドレッシング

このアドレッシングでは、オペランドの実効アドレスは、sp / esp および bp / ebp を除く汎用レジスタのいずれかに置くことができます (これらは、スタック セグメントを操作するための特定のレジスタです)。 コマンドの構文では、このアドレッシング モードは、レジスタ名を角括弧 [] で囲むことによって表されます。 たとえば、命令 mov ax, [ecx] は、レジスタ esx に格納されているオフセットを使用して、データ セグメントのアドレスにあるワードの内容をレジスタ ax に格納します。 レジスタの内容はプログラムの途中で簡単に変更できるため、このアドレス指定方法を使用すると、機械語命令のオペランドのアドレスを動的に割り当てることができます。 このプロパティは、たとえば、循環計算を整理したり、テーブルや配列などのさまざまなデータ構造を操作したりする場合に非常に役立ちます。

オフセットによる間接ベース (レジスタ) アドレッシング

このタイプのアドレッシングは、以前のものに追加されたもので、ベース アドレスに対する既知のオフセットでデータにアクセスするように設計されています。 このタイプのアドレッシングは、プログラム開発の段階で要素のオフセットが事前にわかっていて、構造体のベース (開始) アドレスを動的に計算する必要がある場合に、データ構造体の要素にアクセスするために使用すると便利です。プログラム実行の段階。 ベース レジスタの内容を変更すると、同じタイプのデータ構造の異なるインスタンスにある同じ名前の要素にアクセスできます。

たとえば、命令 mov ax,[edx+3h] は、メモリ領域からレジスタ ax のアドレス (edx + 3h の内容) にワードを転送します。

mov ax、mas [dx]命令は、アドレスのレジスタaxにワードを移動します。dxの内容と識別子masの値(コンパイラは各識別子に、この識別子のオフセットに等しい値を割り当てることに注意してください。データセグメントの始まり)。

オフセットによる間接インデックス アドレッシング

この種のアドレッシングは、オフセットを使用した間接ベース アドレッシングに非常に似ています。 ここでも、汎用レジスタの XNUMX つを使用して実効アドレスを形成します。 しかし、インデックス アドレス指定には、配列を操作するのに非常に便利な興味深い機能が XNUMX つあります。 これは、インデックスレジスタの内容のいわゆるスケーリングの可能性に関連しています。 それは何ですか?

図 20 を見てください。兄弟バイトに注目します。 このバイトの構造について説明する際、XNUMX つのフィールドで構成されていることに注意しました。 これらのフィールドの XNUMX つが ss スケール フィールドで、インデックス レジスタの内容が乗算されます。

たとえば、mov ax,mas[si*2] 命令では、第 2 オペランドの実効アドレスの値は、式 mas+(si)*XNUMX によって計算されます。 アセンブラには配列のインデックス付けを整理する手段がないため、プログラマは自分で整理する必要があります。

スケーリング機能は、この問題の解決に大いに役立ちますが、配列要素のサイズが1、2、4、または8バイトである場合に限ります。

間接ベース インデックス アドレッシング

このタイプのアドレス指定では、実効アドレスは、ベースとインデックスのXNUMXつの汎用レジスタの内容の合計として形成されます。 これらのレジスタは任意の汎用レジスタにすることができ、インデックスレジスタの内容のスケーリングがよく使用されます。

オフセットを使用した間接ベースインデックスアドレス指定

この種のアドレス指定は、間接的なインデックス付きアドレス指定を補完するものです。 実効アドレスは、ベースレジスタの内容、インデックスレジスタの内容、および命令のオフセットフィールドの値のXNUMXつのコンポーネントの合計として形成されます。

たとえば、mov eax、[esi + 5] [edx]命令は、アドレス(esi)+ 5 +(edx)のeaxレジスタにダブルワードを移動します。

add ax,array[esi] [ebx] コマンドは、レジスタ ax の内容をアドレスのワードの内容に追加します: 識別子配列の値 + (esi) + (ebx)。

レクチャーNo.18。チーム

1. データ転送コマンド

実際の適用とその詳細の反映の便宜のために、このグループのコマンドをその機能目的に従って検討する方が便利です。それに従って、コマンドは次のグループのコマンドに分類できます。

1) 汎用データ転送;

2)ポートへの入出力。

3) アドレスとポインタを扱う。

4)データ変換。

5)スタックを操作します。

一般的なデータ転送コマンド

このグループには、次のコマンドが含まれます。

1) mov は基本的なデータ転送コマンドです。 さまざまな配送オプションを実装しています。 このコマンドの詳細に注意してください。

a)movコマンドを使用して、あるメモリ領域から別のメモリ領域に転送することはできません。 このような必要が生じた場合は、現在利用可能な汎用レジスタを中間バッファとして使用する必要があります。

b)メモリからセグメントレジスタに値を直接ロードすることはできません。 したがって、このようなロードを実行するには、中間オブジェクトを使用する必要があります。 これは、汎用レジスタまたはスタックの場合があります。

c)あるセグメントレジスタの内容を別のセグメントレジスタに転送することはできません。 これは、コマンドシステムに対応するオペコードがないためです。 しかし、そのような行動の必要性はしばしば生じます。 このような転送は、中間レジスタと同じ汎用レジスタを使用して実行できます。

d) セグメント レジスタ CS をデスティネーション オペランドとして使用することはできません。 理由は簡単です。 実際には、マイクロプロセッサのアーキテクチャでは、cs: ip ペアには、次に実行する必要があるコマンドのアドレスが常に含まれています。 mov コマンドで CS レジスタの内容を変更することは、実際には転送ではなくジャンプ操作を意味するため、受け入れられません。 2) xchg - 双方向データ転送に使用されます。 もちろん、この操作には一連の mov 命令を使用できますが、交換操作が頻繁に使用されるため、マイクロプロセッサ命令システムの開発者は、別の xchg 交換命令を導入する必要があると考えました。 当然、オペランドは同じ型でなければなりません。 (すべてのアセンブラ命令と同様に) XNUMX つのメモリ セルの内容を互いに交換することはできません。

ポートI/Oコマンド

図 22 を見てください。これは、コンピューター ハードウェア制御の非常に単純化された概念図を示しています。

米。 22. コンピュータハードウェア制御の概念図

図 22 からわかるように、最も低いレベルは BIOS レベルで、ハードウェアはポートを介して直接処理されます。 これにより、機器の独立性の概念が実装されます。 ハードウェアを交換する場合、対応する BIOS 機能を修正し、それらを新しいアドレスとポートのロジックに再配置するだけで済みます。

基本的に、ポートを介してデバイスを直接管理するのは簡単です。 ポート番号、そのビット深度、制御情報形式に関する情報は、デバイスの技術説明に記載されています。 アクションの最終的な目標、特定のデバイスが動作するアルゴリズム、およびそのポートをプログラミングする順序を知る必要があるだけです。つまり、実際には、何をどの順序で送信する必要があるかを知る必要があります。ポート(書き込み時)またはポートからの読み取り(読み取り時)、およびこの情報をどのように解釈するか。 これを行うには、マイクロプロセッサ コマンド システムに存在する XNUMX つのコマンドだけで十分です。

1)アキュムレータでは、port_number-番号port_numberのポートからアキュムレータに入力します。

2) out port, accumulator - アキュムレータの内容を番号 port_number のポートに出力します。

アドレスとメモリポインタを操作するためのコマンド

アセンブラでプログラムを作成する場合、メモリ内のオペランドのアドレスを使用して集中的な作業が行われます。 この種の操作をサポートするために、次のコマンドを含む特別なコマンド グループがあります。

1) lea 宛先、ソース - 有効なアドレスの読み込み。

2) Ids destination, source - データ セグメント レジスタ ds にポインタをロードします。

3)les destination、source-追加のデータセグメントesのレジスタにポインタをロードします。

4)lgs destination、source-追加のデータセグメントgsのレジスタにポインタをロードします。

5) lfs destination, source - ポインタを追加データセグメント fs のレジスタにロードします。

6)lss destination、source-ポインタをスタックセグメントレジスタssにロードします。

lea コマンドは、移動も実行するという点で mov コマンドに似ています。 ただし、lea 命令はデータを転送するのではなく、データの実効アドレス (つまり、データ セグメントの先頭からのデータのオフセット) を転送先オペランドで指定されたレジスタに転送します。

多くの場合、プログラムで何らかのアクションを実行するには、有効なデータアドレスの値だけを知るだけでは不十分ですが、データへの完全なポインタが必要です。 完全なデータポインタは、セグメントコンポーネントとオフセットで構成されます。 このグループの他のすべてのコマンドを使用すると、レジスタのペアのメモリ内のオペランドへのそのような完全なポインタを取得できます。 この場合、アドレスのセグメントコンポーネントが配置されるセグメントレジスタの名前は、オペコードによって決定されます。 したがって、オフセットはデスティネーションオペランドで示される汎用レジスタに配置されます。

しかし、すべてがソース オペランドでそれほど単純であるわけではありません。 実際、コマンドでは、ソースとして、ポインタを受け取りたいメモリ内のオペランドの名前を直接指定することはできません。 最初に、あるメモリ領域のフル ポインタの値を取得し、get コマンドでこの領域の名前のフル アドレスを指定する必要があります。 このアクションを実行するには、メモリーを予約および初期化するためのディレクティブを覚えておく必要があります。

これらのディレクティブを適用する場合、別のデータ定義ディレクティブの名前 (実際には変数の名前) がオペランド フィールドに指定されているという特殊なケースが可能です。 この場合、この変数のアドレスはメモリ内に形成されます。 どのアドレスが生成されるか (有効または完全) は、適用されたディレクティブによって異なります。 dw の場合、実効アドレスの 16 ビット値のみがメモリに形成され、dd の場合、完全なアドレスがメモリに書き込まれます。 メモリ内のこのアドレスの位置は次のとおりです。下位ワードにはオフセットが含まれ、上位ワードにはアドレスの 16 ビット セグメント コンポーネントが含まれます。

たとえば、文字のチェーンを使用して作業を整理する場合、その開始アドレスを特定のレジスタに配置し、チェーンの要素に順次アクセスするためにループでこの値を変更すると便利です。

コマンドを使用してメモリ内の完全なデータ ポインタ、つまり、セグメントのアドレスとセグメント内のオフセット値を取得する必要性は、特にチェーンを操作する場合に発生します。

データ変換コマンド

多くのマイクロプロセッサ命令はこのグループに属していますが、それらのほとんどには、他の機能グループに属している必要がある特定の機能があります。 したがって、マイクロプロセッサ コマンドの全セットのうち、データ変換コマンドに直接起因するコマンドは XNUMX つだけです。 xlat [address_of_transcoding_table]

これは非常に興味深く有用なチームです。 その効果は、al レジスタの値を、recoding_table_address オペランドで指定されたアドレスにあるメモリ テーブルの別のバイトに置き換えることです。

「テーブル」という言葉は非常に条件付きであり、実際、それは単なるバイトの文字列です。 alレジスタの内容を置き換える文字列内のバイトのアドレスは、合計(bx)+(al)によって決定されます。つまり、alの内容はバイト配列のインデックスとして機能します。

xlat コマンドを使用する場合は、次の微妙な点に注意してください。 このコマンドは、新しい値を取得するバイト文字列のアドレスを指定しますが、このアドレスは (たとえば、lea コマンドを使用して) bx レジスタに事前にロードする必要があります。 したがって、lookup_table_address オペランドは実際には必要ありません (オペランドのオプションは、角括弧で囲んで示されます)。 バイト列(トランスコードテーブル)は、1~255バイト(8ビットレジスタの符号なし数値の範囲)のメモリ領域です。

スタック コマンド

このグループは、スタックを使用した柔軟で効率的な作業の整理に焦点を当てた一連の特殊なコマンドです。

スタックは、プログラム データの一時的な保存用に特別に割り当てられたメモリ領域です。 スタックの重要性は、プログラムの構造内に別のセグメントが提供されているという事実によって決まります。 プログラマーが自分のプログラムでスタック セグメントを宣言するのを忘れた場合、tlink リンカーは警告メッセージを発行します。

スタックには XNUMX つのレジスタがあります。

1)ss-スタックセグメントレジスタ。

2) sp/esp - スタック ポインタ レジスタ。

3) bp/ebp - スタック フレーム ベース ポインタ レジスタ。

スタック サイズは、マイクロプロセッサの動作モードによって異なり、64 KB (保護モードでは 4 GB) に制限されています。

一度に使用できるスタックは XNUMX つだけで、そのセグメント アドレスは SS レジスタに含まれています。 このスタックは現在のスタックと呼ばれます。 別のスタックを参照する(「スタックを切り替える」)ためには、別のアドレスを ss レジスタにロードする必要があります。 SS レジスタは、スタック上で動作するすべての命令を実行するためにプロセッサによって自動的に使用されます。

スタックを操作するためのその他の機能をいくつかリストします。

1) スタック上のデータの書き込みと読み取りは、LIFO の原則に従って実行されます。

2) データがスタックに書き込まれると、スタックは下位アドレスに向かって成長します。 この機能は、スタックを操作するコマンドのアルゴリズムに組み込まれています。

3)メモリアドレス指定にesp/spおよびebp/bpレジスタを使用する場合、アセンブラは、それに含まれる値がssセグメントレジスタからのオフセットであると自動的に見なします。

一般に、スタックは図 23 に示すように編成されます。

米。 23.スタック編成の概念図

SS、ESP/SP、および EUR/BP レジスタは、スタックで動作するように設計されています。 これらのレジスタは複雑な方法で使用され、それぞれに独自の機能目的があります。

ESP/SP レジスタは、常にスタックの先頭を指します。つまり、最後の要素がスタックにプッシュされたオフセットが含まれています。 スタック命令は、スタックにプッシュされた最後の要素を常に指すように、このレジスタを暗黙的に変更します。 スタックが空の場合、esp の値は、スタックに割り当てられたセグメントの最後のバイトのアドレスに等しくなります。 要素がスタックにプッシュされると、プロセッサは esp レジスタの値を減らし、その要素を新しい頂点のアドレスに書き込みます。 スタックからデータをポップするとき、プロセッサは頂点アドレスにある要素をコピーし、スタック ポインター レジスタ esp の値をインクリメントします。 したがって、アドレスが減少する方向にスタックが成長することがわかります。

最上位ではなく、スタック内の要素にアクセスする必要がある場合はどうすればよいでしょうか? これを行うには、スタック フレーム ベース ポインタ レジスタである EBP レジスタを使用します。

たとえば、サブルーチンに入るときの典型的なトリックは、必要なパラメーターをスタックにプッシュして渡すことです。 サブルーチンもスタックでアクティブに動作している場合、これらのパラメーターへのアクセスが問題になります。 回避策は、必要なデータをスタック (EUR レジスタ) に書き込んだ後、スタックのフレーム (ベース) ポインタにスタックのトップのアドレスを保存することです。 EUR の値は、後で渡されたパラメーターにアクセスするために使用できます。

スタックの先頭は、より高いメモリ アドレスにあります。 図 23 では、このアドレスはペア ss: fffF で示されています。 ここで、wT のシフトは条件付きで与えられます。 実際には、この値は、プログラマがプログラムでスタック セグメントを記述するときに指定する値によって決定されます。

スタックを使って作業を整理するために、書き込みと読み取りのための特別なコマンドがあります。

1. ソースのプッシュ - ソースの値をスタックの一番上に書き込みます。

興味深いのは、次のアクションを含むこのコマンドのアルゴリズムです (図 24)。

1)(sp)=(sp)-2; spの値は2減少します。

2) ソースからの値は、ss: sp ペアで指定されたアドレスに書き込まれます。

米。 24.プッシュコマンドのしくみ

2.ポップ割り当て-スタックの最上位から宛先オペランドで指定された場所に値を書き込みます。 したがって、値はスタックの最上位から「削除」されます。 popコマンドのアルゴリズムは、pushコマンドのアルゴリズムの逆です(図25)。

1) デスティネーションオペランドによって示される位置にスタックの最上部の内容を書き込みます。

2) (sp) = (sp) + 2; spの値を増やします。

米。 25. pop コマンドの仕組み

3.pusha-スタックへのグループ書き込みコマンド。 このコマンドにより、レジスタax、cx、dx、bx、sp、bp、si、diが順番にスタックに書き込まれます。 spの元の内容、つまり、pushaコマンドが発行される前の内容が書き込まれていることに注意してください(図26)。

米。 26. pusha コマンドの仕組み

4. pushaw は pusha コマンドとほぼ同義ですが、違いは何ですか? ビット属性は、u​​se16 または use32 のいずれかです。 pusha および pushaw コマンドがこれらの各属性でどのように機能するかを見てみましょう。

1)use16-pushawアルゴリズムはpushaアルゴリズムに似ています。

2)use32-pushawは変更されません(つまり、セグメント幅の影響を受けず、常にワードサイズのレジスタ(ax、cx、dx、bx、sp、bp、si、di)で機能します)。 pushaコマンドは、設定されたセグメント幅に敏感であり、32ビットセグメントが指定されている場合、対応する32ビットレジスタ(eax、esx、edx、ebx、esp、ebp、esi、edi)で機能します。

5. pushad-pushaコマンドと同様に実行されますが、いくつかの特性があります。

次のXNUMXつのコマンドは、上記のコマンドの逆を実行します。

1) ローラ;

2) ポポー;

3) ポップ。

以下に説明する一連の命令を使用すると、フラグ レジスタをスタックに保存し、スタックにワードまたはダブル ワードを書き込むことができます。 以下にリストされている命令は、フラグ レジスタの内容全体へのアクセスを許可する (そして要求する) マイクロプロセッサ命令セット内の唯一のものであることに注意してください。

1.pushf-フラグのレジスタをスタックに保存します。

このコマンドの操作は、セグメントサイズ属性によって異なります。

1)16を使用します-サイズが2バイトのフラグレジスタがスタックに書き込まれます。

2) use32 - 4 バイトの eflags レジスタがスタックに書き込まれます。

2. pushfw - フラグのワードサイズのレジスタをスタックに保存します。 use16 属性を持つ pushf のように常に機能します。

3. pushfd - セグメントのビット幅属性に応じて、フラグまたは eflags フラグ レジスタをスタックに保存します (つまり、pushf と同じ)。

同様に、次のXNUMXつのコマンドは、上記の操作の逆を実行します。

1) ポップ;

2) popftv;

3) ポップド。

結論として、スタックの使用がほとんど避けられない場合の主な操作の種類に注意してください。

1) サブルーチンの呼び出し。

2) レジスタ値の一時的な保存。

3) ローカル変数の定義。

2. 算術コマンド

マイクロプロセッサは、整数演算と浮動小数点演算を実行できます。 これを行うために、そのアーキテクチャには XNUMX つの個別のブロックがあります。

1) 整数演算を実行するためのデバイス。

2) 浮動小数点演算を実行するためのデバイス。

これらの各デバイスには、独自のコマンド システムがあります。 原則として、整数デバイスは浮動小数点デバイスの多くの機能を引き継ぐことができますが、これには計算コストがかかります。 アセンブリ言語を使用するほとんどの問題では、整数演算で十分です。

算術命令とデータのグループの概要

整数計算デバイスは、十数個の算術命令をサポートしています。 図 27 は、このグループ内のコマンドの分類を示しています。

米。 27. 演算命令の分類

整数算術命令のグループは、次の XNUMX 種類の数値を処理します。

1) 整数の XNUMX 進数。 数値には符号付きの数字がある場合とない場合があります。つまり、符号付きまたは符号なしの数値です。

2) XNUMX 進数の整数。

これらのデータ型が格納されるマシン形式を検討してください。

整数の XNUMX 進数

固定小数点 XNUMX 進整数は、XNUMX 進数システムでエンコードされた数値です。

8 進整数の次元は、16、32、または 7,15 ビットです。 31 進数の符号は、数値表現の最上位ビットがどのように解釈されるかによって決まります。 これは、対応する次元の数値の 9 または XNUMX 番目のビットです。 同時に、算術コマンドの中で、この最上位ビットを符号 XNUMX として実際に考慮に入れるコマンドが XNUMX つしかないことは興味深いことです。これらは、整数の乗算および除算コマンド imul および idiv です。 それ以外の場合、符号付きの数値、したがって符号ビットを使用したアクションの責任は、プログラマにあります。 XNUMX進数の値の範囲は、そのサイズと、数値の最上位ビットまたは数値の符号ビットとしての最上位ビットの解釈によって異なります(表XNUMX)。

表 9. XNUMX 進数の範囲 XNUMX 進数

28 進数は、数値情報の特殊な表現形式であり、数値の各 XNUMX 進数を XNUMX ビットのグループでエンコードするという原則に基づいています。 この場合、数値の各バイトには、いわゆる XNUMX 進化 XNUMX 進数コード (BCD - Binary-Coded Decimal) で XNUMX 桁または XNUMX 桁の XNUMX 進数が含まれます。 マイクロプロセッサは、BCD 番号を XNUMX つの形式で保存します (図 XNUMX)。

1) パック形式。 この形式では、各バイトに 0 つの 9 進数が含まれます。 4 進数は、4 から 1 までの 00 ビットの 99 進数値です。 この場合、数値の最上位桁のコードが上位 XNUMX ビットを占めます。 したがって、XNUMX バイトでパックされた XNUMX 進数の表現範囲は XNUMX から XNUMX です。

2) パッケージ化されていない形式。 この形式では、各バイトの最下位 4 ビットに 1 進数が 0 桁含まれます。 上位 9 ビットはゼロに設定されます。 いわゆるゾーンです。 したがって、XNUMX 進数のアンパック数を XNUMX バイトで表す範囲は XNUMX から XNUMX です。

米。 28. BCD数の表記

プログラムで XNUMX 進 XNUMX 進数を記述する方法は? これを行うには、db と dt の XNUMX つのデータ記述および初期化ディレクティブのみを使用できます。 これらのディレクティブのみを使用して BCD 番号を記述する可能性があるのは、「下位アドレスの下位バイト」の原則がそのような番号にも適用されるという事実によるものであり、これはそれらの処理に非常に便利です。 一般に、BCD 数値などのデータ型を使用する場合、これらの数値がプログラムで記述される順序とそれらを処理するためのアルゴリズムは、プログラマーの好みと個人的な好みの問題です。 これは、以下の BCD 数値の操作の基本を見れば明らかになります。

XNUMX 進整数の算術演算

符号なし XNUMX 進数の加算

マイクロプロセッサは、255 進数の加算規則に従ってオペランドの加算を実行します。 結果の値がオペランド フィールドの次元を超えない限り、問題はありません。 たとえば、バイト サイズのオペランドを追加する場合、結果が XNUMX を超えてはなりません。これが発生した場合、結果は正しくありません。 なぜこれが起こるのか考えてみましょう。

たとえば、254 + 5 = 259 を 11111110 進数で加算してみましょう。 0000101 + 1 = 00000011 8. 結果は 9 ビットを超え、その正しい値は 8 ビットに収まり、値 3 はオペランドの 0 ビット フィールドに残りましたが、これはもちろん正しくありません。 マイクロプロセッサでは、この追加の結果が予測され、そのような状況を修正して処理するための特別な手段が提供されます。 そのため、この場合のように、結果のビット グリッドを超える状況を修正するために、キャリー フラグ cf が意図されています。 これは、EFLAGS/FLAGS フラグ レジスタのビット XNUMX にあります。 オペランドの上位から XNUMX つが転送されたことを修正するのは、このフラグの設定です。 当然、プログラマーは加算演算の結果がこのような結果になる可能性を考慮に入れ、修正する手段を提供する必要があります。 これには、cf フラグが解析される加算操作の後にコードのセクションを含める必要があります。 このフラグはさまざまな方法で解析できます。

最も簡単でアクセスしやすいのは、jcc 条件分岐コマンドを使用することです。 この命令は、オペランドとして現在のコード セグメント内のラベルの名前を持ちます。 このラベルへの遷移は、前のコマンドの操作の結果として、cf フラグが 1 に設定されている場合に実行されます。マイクロプロセッサーのコマンド システムには、次の XNUMX つのバイナリ加算コマンドがあります。

1) inc オペランド - インクリメント操作、つまり、オペランドの値を 1 増やします。

2) オペランド_1、オペランド_2 を追加 - 演算原理による加算命令: オペランド_1 = オペランド_1 + オペランド_2;

3) adc operand_1、operand_2 - キャリーフラグを考慮した加算命令。 コマンド操作の原則: operand_1 = operand_1 + operand_2 + value_sG。

最後のコマンドに注意してください。これは、上位からの転送を考慮した追加コマンドです。 このようなユニットが出現するメカニズムについては、すでに検討しました。 したがって、adc 命令は、マイクロプロセッサがサポートする標準フィールドの長さを超える次元の長い XNUMX 進数を加算するためのマイクロプロセッサ ツールです。

符号付きバイナリ加算

実際、マイクロプロセッサは符号付き数値と符号なし数値の違いを「認識していません」。 代わりに、彼は計算の過程で発生する特徴的な状況の発生を修正する手段を持っています。 符号なし加算について説明したときに、それらのいくつかを取り上げました。

1) cf キャリー フラグ。1 に設定すると、オペランドが範囲外であることを示します。

2) adc コマンド。このような終了 (最下位ビットからのキャリー) の可能性を考慮します。

もう 11 つの方法は、オペランドの上位 (符号) ビットの状態を登録することです。これは、EFLAGS レジスタ (ビット XNUMX) のオーバーフロー フラグを使用して行われます。

もちろん、コンピューターで数値がどのように表現されるかは覚えています。正は XNUMX 進数、負は XNUMX の補数です。 数を追加するためのさまざまなオプションを検討してください。 例は、オペランドの XNUMX つの最上位ビットの動作と、加算演算の結果の正確さを示すことを目的としています。

30566 = 0111011101100110

+

00687 = 00000010

=

31253 = 01111010

14 桁目と 15 桁目からの転送と結果の正確性を監視します。転送はなく、結果は正しいです。

30566 = 0111011101100110

+

30566 = 0111011101100110

=

1132 = 11101110

14期からの移籍がありました。 15期からの振替はありません。 オーバーフローがあるため、結果は間違っています。数値の値は、16 ビットの符号付き数値 (+32 767) よりも大きいことが判明しました。

-30566 = 10001000 10011010

+

-04875 = 11101100 11110101

=

-35441 = 01110101 10001111

15桁目から乗り換えあり、14桁目から乗り換えなし。 結果は正しくありません。負の数ではなく、正の数であることが判明したためです (最上位ビットは 0)。

-4875 = 11101100 11110101

+

-4875 = 11101100 11110101

=

09750 = 11011001

14 ビット目と 15 ビット目からの転送があります。 結果は正しいです。

したがって、すべてのケースを調査した結果、転送中にオーバーフロー状況 (OF フラグを 1 に設定) が発生することがわかりました。

1) 14 桁目から (符号付き正数の場合);

2) 15 桁目から (負の数の場合)。

逆に、両方のビットにキャリーがある場合、または両方のビットにキャリーがない場合、オーバーフローは発生しません (つまり、OF フラグは 0 にリセットされます)。

したがって、オーバーフローはのオーバーフロー フラグに登録されます。 のフラグに加えて、上位ビットから転送する場合、転送フラグ CF が 1 に設定されます。結果の数字。 条件付きジャンプ命令 JC\JNC および JO\JNO を使用して、それぞれ CF および OF フラグを解析できます。

符号付きの数字を追加するコマンドについては、符号のない数字の場合と同じです。

符号なし XNUMX 進数の減算

加算演算の分析と同様に、減算演算を実行するときに発生するプロセスの本質について説明します。 被減数が減数よりも大きい場合、問題はありません。差は正であり、結果は正しいです。 被減数が減算した値よりも小さい場合、問題があります。結果は 0 未満であり、これはすでに符号付きの数値です。 この場合、結果をラップする必要があります。 これは何を意味するのでしょうか? 通常の減算 (列内) で、最高位から 1 のローンを作成します。 マイクロプロセッサも同じことを行います。つまり、オペランドのビット グリッドの最上位の桁の次の桁から 1 を取ります。 例を挙げて説明しましょう。

05 = 00000000

-10 = 00000000 00001010

減算を行うには、やりましょう

シニア仮想ローン:

100000000 00000101

-

00000000 00001010

=

11111111 11111011

したがって、本質的に、アクションは

(65 + 536) - 5 = 10

ここでの 0 は、いわば 65536 という数字に相当します。もちろん、結果は正しくありませんが、マイクロプロセッサは、キャリー フラグ cf を設定することによってユニットを借りているという事実を修正しますが、すべてが正常であると見なします。 しかし、減算演算の結果をもう一度注意深く見てください。 5 の補数で -5 です。 実験を行ってみましょう: 差を 10 + (-XNUMX) の合計として表します。

5 = 00000000

+

(-10)= 11111111 11110110

=

11111111 11111011

つまり、前の例と同じ結果が得られました。

したがって、符号なし数を減算するコマンドの後、CE フラグの状態を分析する必要があります.1 に設定されている場合、これは上位からの借用があり、結果が追加コードで取得されたことを示します。 .

加算命令と同様に、減算命令のグループは可能な限り最小のセットで構成されます。 これらのコマンドは、現在検討中のアルゴリズムに従って減算を実行します。例外は、プログラマー自身が考慮する必要があります。 減算コマンドには次のものがあります。

1) dec オペランド - デクリメント操作、つまり、オペランドの値を 1 減らします。

2) サブオペランド_1、オペランド_2 - 減算コマンド; その動作原理: operand_1 = operand_1 - operand_2;

3) sbb オペランド_1、オペランド_2 - ローン (ci フラグ) を考慮した減算コマンド: オペランド_1 = オペランド_1 - オペランド_2 - 値_sG。

ご覧のとおり、減算コマンドの中には、キャリー フラグ cf を考慮した sbb コマンドがあります。 このコマンドは adc に似ていますが、現在、cf フラグは、数値を減算するときに最上位桁から 1 を借用するインジケータとして機能します。

符号付きバイナリ減算

ここでは、すべてがやや複雑です。 マイクロプロセッサは、加算と減算の 45 つのデバイスを持つ必要はありません。 追加デバイスが 127 つあれば十分です。 ただし、追加コードで符号付きの数値を加算する方法による減算の場合、両方のオペランド (縮小されたものと減算されたものの両方) を表す必要があります。 結果も XNUMX の補数値として扱う必要があります。 しかし、ここで困難が生じます。 まず第一に、それらはオペランドの最上位ビットが符号ビットと見なされるという事実に関連しています。 XNUMX - (-XNUMX) を引く例を考えてみましょう。

符号付き数の減算 1

45 = 0010

-

-127 = 1000 0001

=

-44 = 1010 1100

符号ビットから判断すると、結果は負であることが判明しました。これは、数値が -44 に等しい補数と見なされるべきであることを示しています。 正しい結果は 172 になるはずです。ここでは、符号付き加算の場合と同様に、数値の有効ビットがオペランドの符号ビットを変更したときに仮数オーバーフローが発生しました。 この状況は、のオーバーフロー フラグの内容で追跡できます。 これを 1 に設定すると、結果がこのサイズのオペランドの符号付き数値の範囲外である (つまり、最上位ビットが変更された) ことを示し、プログラマーは結果を修正するためのアクションを実行する必要があります。

符号付き数の減算 2

-45-45 = -45 + (-45) = -90。

-45 = 11010011

+

-45 = 11010011

=

-90 = 1010 0110

ここではすべて問題なく、のオーバーフロー フラグは 0 にリセットされ、符号ビットの 1 は結果値が XNUMX の補数であることを示します。

大きなオペランドの減算と加算

お気付きかもしれませんが、加算命令と減算命令は固定次元のオペランド (8、16、32 ビット) で機能します。 しかし、48 ビットのオペランドを使用して、たとえば 16 ビットなど、より大きな次元の数値を加算する必要がある場合はどうでしょうか。 たとえば、48 つの XNUMX ビット数値を加算してみましょう。

米。 29. 大きなオペランドの追加

図 29 は、長い数を段階的に加算する技術を示しています。 マルチバイト数を加算するプロセスは、1 つの数値を「列内」で加算する場合と同じ方法で行われることがわかります。必要に応じて、最上位ビットに XNUMX を転送する実装が行われます。 このプロセスをプログラムすることができれば、加算および減算演算を実行できる XNUMX 進数の範囲が大幅に拡大されます。

オペランドの標準ビット グリッドを超える表現範囲を持つ数値を減算する原理は、加算の場合と同じです。つまり、キャリー フラグ cf が使用されます。 列で減算するプロセスを想像し、マイクロプロセッサ命令を sbb 命令と正しく組み合わせる必要があるだけです。

加算命令と減算命令の説明を締めくくるには、cf フラグと of フラグに加えて、バイナリ算術命令で使用できる eflags レジスタにいくつかのフラグがあります。 これらは次のフラグです。

1) zf - ゼロ フラグ。演算の結果が 1 の場合は 0 に設定され、結果が 1 でない場合は 0 に設定されます。

2) sf - 算術演算後の値が結果の最上位ビットの値、つまりビット 7、15、または 31 と一致する (だけでなく) 符号フラグ。したがって、このフラグは演算に使用できます。署名された番号について。

符号なし数の乗算

符号なし数を乗算するコマンドは次のとおりです。

マルチファクター_1

ご覧のとおり、コマンドには乗数オペランドが 2 つしか含まれていません。 第 10 オペランド factor_XNUMX は暗黙的に指定されます。 その位置は固定されており、因子のサイズによって異なります。 一般に、乗算の結果はどの因数よりも大きいため、そのサイズと位置も一意に決定する必要があります。 係数のサイズ、および第 XNUMX オペランドと結果の配置のオプションを表 XNUMX に示します。

表 10. オペランドの配置と乗算の結果

表から、積が 2 つの部分で構成され、オペランドのサイズに応じて、factor_XNUMX の代わりに (下の部分)、追加のレジスタ ah、dx、edx (上の部分) に配置されることがわかります。部)。 では、動的に (つまり、プログラムの実行中に)、結果が XNUMX つのレジスターに収まるほど小さいこと、またはレジスターの次元を超えて最高部分が別のレジスターに収まることを知るにはどうすればよいでしょうか? これを行うには、前の説明で既に知られている cf フラグとオーバーフロー フラグを使用します。

1) 結果の先頭部分がゼロの場合、積演算の後、フラグ cf = 0 および of = 0;

2) これらのフラグが XNUMX 以外の場合、これは、結果が製品の最小部分を超えており、XNUMX つの部分で構成されていることを意味します。これは、今後の作業で考慮する必要があります。

符号付き数値の乗算

数値に符号を掛けるコマンドは次のとおりです。

[imul オペランド_1、オペランド_2、オペランド_3]

このコマンドは、mul コマンドと同じ方法で実行されます。 imulコマンドの際立った特徴は、記号の形成のみです。

結果が小さく、0 つのレジスタに収まる場合 (つまり、cf = of = 1 の場合)、他のレジスタの内容 (上位部分) は符号拡張されます。そのすべてのビットは上位ビット (符号ビット) と等しくなります。 ) 結果の低い部分。 それ以外の場合 (cf = of = XNUMX の場合)、結果の符号は結果の上位部分の符号ビットであり、下位部分の符号ビットはバイナリ結果コードの有効ビットです。

符号なし数の除算

符号なし数を除算するコマンドは次のとおりです。

div ディバイダー

除数はメモリ内またはレジスタ内にあり、サイズは 8、16、または 32 ビットです。 被除数の位置は固定されており、乗算命令と同様に、オペランドのサイズによって異なります。 除算コマンドの結果は、商と剰余の値です。

除算演算のオペランドの位置とサイズのオプションを表 11 に示します。

表 11. オペランドの配置と除算の結果

除算命令実行後、フラグの内容は不定ですが、0 除算と呼ばれる割り込み番号 XNUMX が発生する場合があります。 このタイプの中断は、いわゆる例外に属します。 この種の割り込みは、演算処理中の何らかの異常により、マイクロプロセッサ内で発生します。 div コマンドの実行中に割り込み O、「ゼロで割る」は、次のいずれかの理由で発生する可能性があります。

1) 除数がゼロです。

2) 割り当てられたビット グリッドに商が含まれていません。これは、次の場合に発生する可能性があります。

a) ワードの値を持つ被除数をバイトの値を持つ除数で割る場合、被除数の値は除数の値の 256 倍を超えます。

b) ダブルワードの値を持つ被除数をワードの値を持つ除数で割る場合、被除数の値は除数の値の 65 倍を超えます。

c) 4 倍ワード値の被除数を 294 倍ワード値の除数で割り、被除数の値が除数の値の 967 倍を超える場合。

記号付きの分割

数字を符号で割るコマンドは次のとおりです。

Idiv ディバイダー

このコマンドについては、コマンドと符号付き番号に関して考慮されたすべての規定が有効です。 符号付きの数値の場合、例外 0、「ゼロによる除算」の発生の特徴のみに注目します。 次のいずれかの理由で idiv コマンドを実行すると発生します。

1) 除数がゼロです。

2) 割り当てられたビットグリッドに商が含まれていない。

後者は次のように発生する可能性があります。

1) 符号付きワード値の被除数を符号付きバイト値の除数で割り、被除数の値が除数の値の 128 倍を超える場合 (したがって、商は -128 からの範囲外であってはなりません) + 127まで);

2) 被除数を符号付きダブルワード値で除数を符号付きワード値で除算し、被除数の値が除数の値の 32 倍を超える場合 (したがって、商は - の範囲外であってはなりません。 768 ~ +32) ;

3) 被除数を符号付きダブルワード除数で符号付きクワッドワード値で割り、被除数の値が除数の値の 2 倍を超える場合 (したがって、商は -147 ~ + の範囲外であってはなりません) 483 648 2 147)。

整数演算の補助命令

マイクロプロセッサの命令セットには、算術計算を実行するアルゴリズムのプログラミングを容易にする命令がいくつかあります。 それらにはさまざまな問題が発生する可能性があり、その解決のためにマイクロプロセッサの開発者はいくつかのコマンドを提供しています。

型変換コマンド

算術演算に含まれるオペランドのサイズが異なる場合はどうなりますか? たとえば、加算演算で、64 つのオペランドがワードで、もう XNUMX つのオペランドがダブル ワードであるとします。 同じ形式のオペランドは加算演算に参加する必要があると前述しました。 数値が符号なしの場合、出力は簡単に見つけることができます。 この場合、元のオペランドに基づいて、新しいオペランド (ダブルワード形式) を形成できます。その上位ビットは、単にゼロで埋めることができます。 符号付きの数値の場合、状況はより複雑になります。プログラムの実行中にオペランドの符号を動的に考慮する方法は? このような問題を解決するために、マイクロプロセッサの命令セットには、いわゆる型変換命令があります。 これらの命令は、バイトをワードに、ワードをダブルワードに、ダブルワードをクワッドワード (XNUMX ビット値) に展開します。 型変換命令は、新しく構築されたオペランドの上位ビットを古いオブジェクトの符号ビットの値で自動的に埋めるため、符号付き整数を変換するときに特に役立ちます。 この操作により、元の値と同じ符号と大きさの整数値が得られますが、より長い形式になります。 このような変換は符号伝搬操作と呼ばれます。

型変換コマンドには XNUMX 種類あります。

1. オペランドのない命令。 これらのコマンドは、固定レジスターで機能します。

1)cbw(Convert Byte to Word)-上位ビットa1の値をahレジスタのすべてのビットに拡散することによって、バイト(a1レジスタ内)をワード(ahレジスタ内)に変換するコマンド。

2)cwd(Convert Word to Double)-上位ビットaxの値をレジスタdxのすべてのビットに拡散することによって、ワード(レジスタax内)をダブルワード(レジスタdx:ax内)に変換するコマンド。

3) cwde (Convert Word to Double) - 上位ビット ax の値を eax レジスタの上位半分のすべてのビットに拡散することにより、ワード (レジスタ ax 内) をダブルワード (レジスタ eax 内) に変換するコマンド;

4) cdq (ダブルワードをクォーターワードに変換) - eax の最上位ビットの値をedx レジスタのビット。

2. 文字列処理コマンドに関連するコマンド movsx および movzx。 これらのコマンドには、問題のコンテキストで役立つプロパティがあります。

1) movsx operand_1、operand_2 - 符号伝搬で送信します。 符号ビットの値を使用して、operand_8 の上位の位置を埋めて、operand_16 の 2 ビットまたは 16 ビットの値 (レジスタまたはメモリ オペランド) をレジスタの 32 つの 1 ビットまたは XNUMX ビットの値に拡張します。 この命令は、算術演算用の符号付きオペランドを準備するのに役立ちます。

2) movzx operand_1、operand_2 - 拡張子ゼロで送信します。 operand_8 の 16 ビットまたは 2 ビットの値を 16 ビットまたは 32 ビットに拡張し、operand_2 の上位位置をゼロでクリア (埋め) します。 この命令は、演算用の符号なしオペランドを準備するのに役立ちます。

その他の便利なコマンド

1. xadd 宛先、ソース - 交換と追加。

このコマンドを使用すると、次の XNUMX つのアクションを順番に実行できます。

1) 宛先と送信元の値を交換します。

2) 合計の代わりにデスティネーション オペランドを配置します: デスティネーション = デスティネーション + ソース。

2. neg オペランド - XNUMX の補数による否定。

この命令は、オペランドの値を反転します。 物理的には、コマンドは次の XNUMX つのアクションを実行します。

オペランド = 0 - オペランド、つまりゼロからオペランドを減算します。

neg オペランド コマンドを使用できます。

1) 記号を変更する。

2) 定数から減算を実行します。

XNUMX 進数と XNUMX 進数の算術演算

このセクションでは、パックされた BCD 数値とアンパックされた BCD 数値の XNUMX つの基本的な算術演算のそれぞれの詳細を見ていきます。

なぜBCD番号が必要なのですか? 答えは次のとおりです。BCD 数値は、ビジネス アプリケーション、つまり数値が大きく正確である必要がある場合に必要です。 XNUMX 進数の例で既に見たように、そのような数値を使用した演算は、アセンブリ言語にとって非常に問題があります。 XNUMX 進数の使用には、次のような欠点があります。

1) ワードおよびダブルワード形式の値には範囲が制限されています。 プログラムが金融の分野で機能するように設計されている場合、ルーブルの金額を65(単語の場合)または536(ダブルワードの場合)に制限すると、その適用範囲が大幅に狭まります。

2) 丸め誤差の存在。 銀行のどこかで実行されているプログラムが、XNUMX 進整数を操作するときに残高の値を考慮せず、数十億を操作することを想像できますか? 私はそのようなプログラムの作成者にはなりたくありません。 浮動小数点数を使用しても保存されません。同じ丸めの問題が存在します。

3) シンボリック形式 (ASCII コード) での大量の結果の表示。 ビジネス プログラムは計算を行うだけではありません。 それらの使用目的の 30 つは、ユーザーに情報を迅速に配信することです。 もちろん、これを行うには、情報を記号形式で提示する必要があります。 数値を XNUMX 進数から ASCII に変換するには、ある程度の計算作業が必要です。 浮動小数点数を記号形式に変換するのはさらに困難です。 しかし、アンパックされた XNUMX 進数とそれに対応する ASCII テーブルの文字の XNUMX 進数表現を見ると、それらが XNUMXh 異なっていることがわかります。 したがって、シンボリック形式への変換およびその逆は、はるかに簡単かつ高速です。

少なくとも XNUMX 進数を使ったアクションの基本をマスターすることの重要性は、おそらくすでにおわかりでしょう。 次に、XNUMX 進数で基本的な算術演算を実行する機能について考えます。 BCD 数の加算、減算、乗算、および除算のための個別のコマンドがないという事実にすぐに注意してください。 これは非常に理解できる理由で行われました。そのような数の次元は任意に大きくなる可能性があります。 BCD 数値は、パックされたものとアンパックされたものの両方で足し算と引き算ができますが、アンパックされた BCD 数値のみが除算と乗算ができます。 なぜそうなるのかは今後の議論で明らかになるだろう。

アンパックされた BCD 数値の算術演算

アンパックされた BCD 番号を追加する

追加の XNUMX つのケースを考えてみましょう。

足し算の結果が9以下

6 = 0000

+

3 = 0000

=

9 = 0000

ジュニアからシニアテトラッドへの移籍はありません。 結果は正しいです。

加算の結果が 9 より大きい:

06 = 0000

+

07 = 0000

=

13 = 0000

BCD番号を受け取っていません。 結果は間違っています。 アンパックされた BCD 形式の正しい結果は、0000 進数で 0001 0000 0011 13 (または XNUMX 進数で XNUMX) になります。

BCD 数値を追加する際の問題 (および他の算術演算を実行する際の同様の問題) と、それを解決するための可能な方法を分析した後、マイクロプロセッサ コマンド システムの開発者は、BCD 数値を操作するための特別なコマンドを導入するのではなく、いくつかの修正コマンドを導入することを決定しました。 .

これらの命令の目的は、オペランドが BCD 数である場合に、通常の算術命令の演算結果を修正することです。

例 10 の減算の場合、得られた結果を修正する必要があることがわかります。 マイクロプロセッサ コマンド システムで XNUMX つの XNUMX 桁のパックされていない BCD 番号を加算する操作を修正するために、特別なコマンド aaa (加算の ASCII 調整) があります。

この命令にはオペランドがありません。 これは暗黙のうちに al レジスタでのみ機能し、下位のテトラッドの値を解析します。

1) この値が 9 未満の場合、フラグ cf は XNUMX にリセットされ、次の命令への移行が実行されます。

2) この値が 9 より大きい場合、次のアクションが実行されます。

a) 下位テトラッドの内容に 6 が追加されます (ただし、レジスタ全体の内容には追加されません!)。したがって、XNUMX 進数の結果の値は正しい方向に修正されます。

b) フラグ cf が 1 に設定され、それにより転送が最上位ビットに固定され、後続のアクションで考慮できるようになります。

したがって、例 10 では、合計値 0000 1101 が al にあると仮定すると、aaa 命令の後、レジスタは 1101 + 0110 = 0011、つまりバイナリ 0000 0011 または 3 進数 1 になり、cf フラグは XNUMX に設定されます。つまり、転送はマイクロプロセッサに保存されています。 次に、プログラマは adc 加算命令を使用する必要があります。これは、前のビットからのキャリーを考慮に入れます。

アンパックされた BCD 数の減算

ここでの状況は足し算によく似ています。 同じケースを考えてみましょう。

減算の結果は 9 以下です。

6 = 0000

-

3 = 0000

=

3 = 0000

ご覧のとおり、先輩ノートからの貸与はありません。 結果は正しく、修正は必要ありません。

減算の結果が 9 より大きい:

6 = 0000

-

7 = 0000

=

-1 = 1111 1111

減算は、バイナリ算術の規則に従って実行されます。 したがって、結果は BCD 数ではありません。

アンパックされた BCD 形式の正しい結果は 9 (バイナリで 0000 1001) です。 この場合、通常の減算コマンドと同様に、最上位桁からの借用が想定されます。つまり、BCD 数の場合、実際には 16 - 7 の減算が実行されます。したがって、次のように、加算の場合、減算結果を修正する必要があります。 このために、特別なコマンド aas (ASCII Adjust for Substraction) があります。これは、シンボリック形式で表現するための減算結果の修正です。

aas 命令にもオペランドがなく、al レジスタで動作し、次のように最小次数のテトラッドを解析します。

1) その値が 9 未満の場合、cf フラグは 0 にリセットされ、制御は次のコマンドに移されます。

2) al の tetrad 値が 9 より大きい場合、aas コマンドは次のアクションを実行します。

a) レジスタ a6 の下位テトラッドの内容から XNUMX を減算します (注意 - レジスタ全体の内容からではありません)。

b) レジスタ aXNUMX の上位テトラッドをリセットします。

c) cf フラグを 1 に設定し、仮想の上位ボローを修正します。

aas コマンドが基本的な sub および sbb 減算コマンドと組み合わせて使用​​されることは明らかです。 この場合、オペランドの最下位桁を減算するときにサブコマンドを XNUMX 回だけ使用するのが理にかなっています。次に、sbb コマンドを使用する必要があります。これにより、最上位からのローンの可能性が考慮されます。

アンパックされた BCD 数の乗算

アンパックされた数値の加算と減算の例を使用すると、BCD 数値に対してこれらの演算を実行するための標準アルゴリズムが存在しないことが明らかになりました。プログラマは、プログラムの要件に基づいて、これらの演算を自分で実装する必要があります。

残りの XNUMX つの演算 (乗算と除算) の実装はさらに複雑です。 マイクロプロセッサの命令セットには、XNUMX 桁のパックされていない BCD 数の乗算と除算を生成するための手段しかありません。

任意の次元の数値を乗算するには、「列内」などの乗算アルゴリズムに基づいて、自分で乗算プロセスを実装する必要があります。

XNUMX つの XNUMX 桁の BCD 数を乗算するには、次のことを行う必要があります。

1) 要素の XNUMX つを AL レジスタに配置します (mul 命令で必要な場合)。

2) 第 XNUMX オペランドをレジスタまたはメモリに配置し、バイトを割り当てます。

3) mul コマンドで係数を掛けます (予想どおり、結果は ah になります)。

4) もちろん、結果はバイナリ コードになるため、修正する必要があります。

乗算後の結果を修正するには、特別なコマンドが使用されます - aam (ASCII 乗算の調整) - シンボリック形式で表現するために乗算の結果を修正します。

これにはオペランドがなく、次のように AX レジスタで動作します。

1) al を 10 で割ります。

2) 割り算の結果は次のように書きます: al は商、ah は剰余。 その結果、aam 命令の実行後、AL および ah レジスターには、XNUMX 桁の積の正しい BCD 桁が含まれます。

aam コマンドの説明を終える前に、このコマンドの使用法をもう 0 つメモしておく必要があります。このコマンドを使用すると、AL レジスタ内の 99 進数をアンパック BCD 数値に変換でき、これが ah レジスタに配置されます。結果の最上位桁は ah に、最下位桁は al に格納されます。 XNUMX 進数が XNUMX ~ XNUMX の範囲内になければならないことは明らかです。

アンパックBCD数の分割

アンパックされた XNUMX つの BCD 数値の除算演算を実行するプロセスは、以前に検討した他の演算とは多少異なります。 ここでも修正アクションが必要ですが、XNUMX つの BCD 番号を別の BCD 番号で直接除算するメイン操作の前に実行する必要があります。 まず、レジスタ ah で、被除数のアンパックされた XNUMX 桁の BCD を取得する必要があります。 これにより、プログラマーはある意味快適になります。 次に、コマンド aad - aad (ASCII Adjust for Division) - 記号表現の除算補正を発行する必要があります。

この命令にはオペランドがなく、ax レジスタ内の 0 桁のアンパック BCD 番号を 99 進数に変換します。この XNUMX 進数は、除算演算の被除数の役割を果たします。変換に加えて、aad コマンドは、結果の XNUMX 進数を AL レジスタに置きます。被除数は当然、XNUMX ~ XNUMX の範囲の XNUMX 進数になります。

aad コマンドがこの変換を実行するアルゴリズムは次のとおりです。

1) ah の元の BCD 番号の最上位桁 (AH の内容) に 10 を掛けます。

2) 加算 AH + AL を実行し、その結果 (XNUMX 進数) を AL に入力します。

3) AN の内容をリセットします。

次に、プログラマは通常の div 除算コマンドを発行して、ax の内容をバイト レジスタまたはバイト メモリ位置にある XNUMX つの BCD 数字で除算する必要があります。

aash と同様に、aad コマンドを使用して、アンパックされた BCD 数値を 0 ~ 99 の範囲から等価なバイナリに変換することもできます。

より大きな容量の数を分割するには、乗算の場合と同様に、「列内」などの独自のアルゴリズムを実装するか、より最適な方法を見つける必要があります。

パックされた BCD 数値の算術演算

前述のように、パックされた BCD 数値は加算と減算のみ可能です。 それらに対して他のアクションを実行するには、さらにアンパック形式またはバイナリ表現に変換する必要があります。 パックされた BCD 数はあまり重要ではないため、簡単に検討します。

パックされた BCD 番号の追加

まず、問題の核心に迫り、XNUMX 桁のパック BCD 番号を XNUMX つ加算してみます。 パックされた BCD 番号を追加する例:

67 = 01100111

+

75 = 01110101

=

142 = 1101 1100 = 220

ご覧のとおり、1101 進数では 1100 220 (0001 進数では 0100) となり、これは正しくありません。 これは、マイクロプロセッサが BCD 番号の存在を認識せず、0010 進数の加算規則に従って加算するためです。 実際には、BCD の結果は 142 XNUMX XNUMX (または XNUMX 進数で XNUMX) になるはずです。

アンパックされた BCD 数と同様に、パックされた BCD 数については、何らかの方法で算術演算の結果を修正する必要があることがわかります。

マイクロプロセッサーは、このコマンドを提供します。

daa コマンドは、daa コマンドの説明にあるアルゴリズムに従って、al レジスタの内容を 99 つのパック XNUMX 進数に変換します. 結果の単位 (加算の結果が XNUMX より大きい場合) は、cf フラグに格納されます。これにより、最上位ビットへの転送が考慮されます。

パックされた BCD 数の減算

加算と同様に、マイクロプロセッサはパックされた BCD 数を XNUMX 進数として扱い、それに応じて BCD 数を XNUMX 進数として減算します。

パックされた BCD 数の減算。

67-75 を引きましょう。 マイクロプロセッサは加算の方法で減算を実行するため、次のようになります。

67 = 01100111

+

-75 = 10110101

=

-8 = 0001 1100 = 28

ご覧のとおり、結果は 28 進数で 0000 になり、これはばかげています。 BCD では、結果は 1000 8 (または XNUMX 進数で XNUMX) になります。

パックされた BCD 数の減算をプログラミングする場合、プログラマはアンパックされた BCD 数を減算する場合と同様に、符号を自分で制御する必要があります。 これは、上位ボローを修正する CF フラグを使用して行われます。

BCD 数自体の減算は、単純な sub または sbb 減算コマンドによって実行されます。 結果の修正は、コマンド das - das (減算の XNUMX 進調整) - XNUMX 進形式で表現するための減算結果の修正によって実行されます。

das コマンドは、das コマンドの説明で説明されているアルゴリズムに従って、AL レジスターの内容を XNUMX つのパック XNUMX 進数に変換します。

LECTURE No.19. コントロール転送コマンド

1. ロジックコマンド

マイクロプロセッサのコマンドシステムは、算術演算の手段に加えて、論理データ変換の手段も備えています。 形式論理の規則に基づく論理的な手段によるデータ変換。

形式論理は、true ステートメントと false ステートメントのレベルで動作します。 マイクロプロセッサの場合、これは通常、それぞれ 1 と 0 を意味します。 コンピュータの場合、XNUMX と XNUMX の言語はネイティブですが、機械語命令が処理するデータの最小単位はバイトです。 ただし、システム レベルでは、多くの場合、可能な限り低いレベルであるビット レベルで動作できる必要があります。

米。 29. 論理データ処理の手段

論理データ変換の手段には、論理コマンドと論理操作が含まれます。 一般に、アセンブラ命令のオペランドは、演算子とオペランドの組み合わせである式にすることができます。 これらの演算子の中には、式オブジェクトに論理演算を実装する演算子が含まれる場合があります。

これらのツールについて詳しく検討する前に、論理データ自体とは何か、それらに対してどのような操作が実行されるかを考えてみましょう。

ブールデータ

論理データ処理の理論的基礎は形式論理です。 ロジックにはいくつかのシステムがあります。 最も有名なものの XNUMX つは、命題計算です。 命題とは、真または偽のいずれかであると言える任意のステートメントです。

命題計算は、命題のいくつかの組み合わせの真偽を決定するために使用される一連の規則です。

命題計算は、コンピューターの原理とそのプログラミングの基本的な方法と非常に調和して組み合わされています。 コンピュータのすべてのハードウェア コンポーネントは、ロジック チップ上に構築されています。 最下層のコンピュータで情報を表現するためのシステムは、ビットの概念に基づいています。 0 つの状態 (1 (偽) と XNUMX (真)) しか持たないビットは、命題計算に自然に適合します。

理論によれば、次の論理演算はステートメント (ビット) に対して実行できます。

1. 否定 (論理否定) - XNUMX つのオペランドに対する論理演算で、その結果は元のオペランドの値の逆数です。

この操作は、次の真理値表 (表 12) によって一意に特徴付けられます。

表 12. 論理否定の真理値表

2. 論理和 (論理的包括的 OR) - 1 つのオペランドの論理演算。その結果は、一方または両方のオペランドが真 (1) の場合は「真」 (0) になり、両方のオペランドが真 (0) の場合は「偽」 (XNUMX) になります。偽 (XNUMX)。

この動作は、次の真理値表 (表 13) を使用して説明されます。

表 13. 論理包含 OR の真理値表

3. 論理乗算 (論理 AND) - 1 つのオペランドに対する論理演算で、両方のオペランドが真 (1) の場合にのみ結果が真 (0) になります。 それ以外の場合、操作の値は「false」(XNUMX) です。

この動作は、次の真理値表 (表 14) を使用して説明されます。

表 14. 論理 AND 真理値表

4. 論理排他的加算 (論理排他的 OR) - 1 つのオペランドの論理演算。その結果は、1 つのオペランドのうちの 0 つだけが真 (0) の場合は「真」(1)、場合は偽 (15) です。両方のオペランドは、偽 (XNUMX) または真 (XNUMX) のいずれかです。 この動作は、次の真理値表 (表 XNUMX) を使用して説明されます。

表 15. 論理 XOR の真理値表

マイクロプロセッサの命令セットには、これらの操作をサポートする 16 つの命令が含まれています。 これらの命令は、オペランドのビットに対して論理演算を実行します。 もちろん、オペランドの次元は同じでなければなりません。 たとえば、オペランドの次元がワード (0 ビット) に等しい場合、最初にオペランドのゼロ ビットに対して論理演算が実行され、その結果が結果のビット XNUMX の代わりに書き込まれます。 次に、コマンドはこれらの動作を XNUMX 番目から XNUMX 番目までのすべてのビットに対して順次繰り返します。

ロジックコマンド

マイクロプロセッサのコマンド システムには、論理データの処理をサポートする次の一連のコマンドがあります。

1) および operand_1、operand_2 - 論理乗算演算。 このコマンドは、オペランド operand_1 および operand_2 のビットに対してビット単位の論理 AND 演算 (結合) を実行します。 結果は operand_1 の代わりに書き込まれます。

2) og operand_1, operand_2 - 論理加算演算。 このコマンドは、オペランド operand_1 および operand_2 のビットに対してビットごとの論理 OR 演算 (論理和) を実行します。 結果は operand_1 の代わりに書き込まれます。

3) xor operand_1、operand_2 - 論理排他的加算の演算。 このコマンドは、オペランド operand_1 および operand_2 のビットに対してビットごとの論理 XOR 演算を実行します。 結果はオペランドの代わりに書き込まれます。

4)オペランド_1、オペランド_2のテスト - 「テスト」演算(論理積法を使用)。 このコマンドは、オペランド operand_1 および operand_2 のビットに対してビット単位の論理 AND 演算を実行します。 オペランドの状態は同じままで、フラグ zf、sf、および pf のみが変更されます。これにより、状態を変更せずにオペランドの個々のビットの状態を分析できます。

5) notオペランド - 論理否定の演算。 このコマンドは、オペランドの各ビットのビット単位の反転 (反対の値で値を置き換える) を実行します。 結果はオペランドの代わりに書き込まれます。

マイクロプロセッサの命令セットにおける論理コマンドの役割を理解するには、そのアプリケーションの領域とプログラミングでの一般的な使用方法を理解することが非常に重要です。

論理コマンドを使用すると、オペランドの個々のビットを選択して、設定、リセット、反転、または単に特定の値をチェックすることができます。

このような作業をビットで編成するために、通常、operand_2 はマスクの役割を果たします。 ビット 1 に設定されたこのマスクのビットを使用して、特定の操作に必要なオペランド_1 ビットが決定されます。 この目的で使用できる論理コマンドを示しましょう。

1) 特定の桁 (ビット) を 1 に設定するには、コマンド og operand_1, operand_2 を使用します。

この命令では、マスクとして機能するオペランド_2 に、オペランド_1 で 1 に設定する必要があるビットの代わりに XNUMX ビットを含める必要があります。

2) 特定の桁 (ビット) を 0 にリセットするには、コマンドとオペランド_1、オペランド_2 を使用します。

この命令では、マスクとして機能するオペランド_2 には、オペランド_0 で 1 に設定する必要があるビットの代わりにゼロ ビットを含める必要があります。

3) コマンド xor operand_1、operand_2 が適用されます。

a) operand_1 とオペランドのどのビットが異なるかを調べる。

b) operand_1 で指定されたビットの状態を反転します。

xor コマンドを実行するときに必要なマスク ビット (operand_2) は XNUMX でなければならず、残りは XNUMX でなければなりません。

コマンド test operand_1, operand_2 (check operand_1) は、指定されたビットのステータスをチェックするために使用されます。

マスク (operand_1) 内の operand_2 のチェックされたビットは、1 に設定する必要があります。 test コマンドのアルゴリズムは and コマンドのアルゴリズムと似ていますが、operand_XNUMX の値は変更されません。 コマンドの結果は、ゼロ フラグ zf の値を設定することです。

1) zf = 0 の場合、論理積の結果としてゼロの結果が得られます。つまり、オペランドの対応する単位ビットと一致しなかったマスクの XNUMX 単位ビットです。

2) zf = 1 の場合、論理積の結果として、1 以外の結果が得られます。つまり、マスクの少なくとも XNUMX つの単位ビットが、operand_XNUMX の対応する単位ビットと一致します。

テスト コマンドの結果に反応するには、ジャンプ コマンド jnz label (Jump if Not Zero) - ゼロ フラグ zf が非ゼロの場合はジャンプ、または逆のアクション コマンド - jz label (Jump if Zero) を使用することをお勧めします。 ) - ゼロ フラグ zf = 0 の場合にジャンプします。

次の 1 つのコマンドは、XNUMX に設定された最初のオペランド ビットを検索します。 検索は、オペランドの最初と最後から実行できます。

1) bsf operand_1、operand_2 (ビット スキャン順方向) - ビットを順方向にスキャンします。 この命令は、2 に設定された最初のビットを検索するために、operand_0 のビットを最下位から最上位 (ビット 1 から最上位ビット) に検索 (スキャン) します。このビットを整数値として。 operand_1 のすべてのビットが 2 の場合、ゼロ フラグ zf が 0 に設定されます。それ以外の場合、zf フラグは 1 にリセットされます。

2) bsr operand_1、operand_2 (ビット スキャン リセット) - 逆の順序でビットをスキャンします。 この命令は、2 に設定された最初のビットを検索するために、operand_0 のビットを最上位から最下位 (最上位ビットからビット 1) に検索 (スキャン) します。このビットを整数値として。 左側の最初のユニット ビットの位置がビット 1 に対してカウントされることが重要です。operand_0 のすべてのビットが 2 の場合、ゼロ フラグ zf が 0 に設定され、そうでない場合、zf フラグは 1 にリセットされます。

Intel マイクロプロセッサの最新モデルでは、オペランドの特定のビットにアクセスできるようにする論理命令のグループに、さらにいくつかの命令が登場しました。 オペランドは、メモリ内または汎用レジスタ内のいずれかになります。 ビット位置は、オペランドの最下位ビットに対するビット オフセットによって指定されます。 オフセット値は、直接値として指定するか、汎用レジスターに含めることができます。 bsr および bsf コマンドの結果をオフセット値として使用できます。 すべての命令は、選択したビットの値を CE フラグに割り当てます。

1) bt オペランド、bit_offset (ビット テスト) - ビット テスト。 この命令は、ビット値を cf フラグに転送します。

2) bts オペランド、offset_bit (ビット テストとセット) - ビットのチェックと設定。 この命令は、ビット値を CF フラグに転送し、チェックするビットを 1 に設定します。

3) btr オペランド、bit_offset (ビット テストとリセット) - ビットのチェックとリセット。 この命令はビット値を CF フラグに転送し、このビットを 0 に設定します。

4) btc オペランド、offset_bit (ビット テストと変換) - ビットのチェックと反転。 この命令は、cf フラグのビットの値をラップしてから、そのビットの値を反転します。

シフト コマンド

このグループの命令は、オペランドの個々のビットの操作も提供しますが、上記の論理命令とは異なる方法で行います。

すべてのシフト命令は、オペコードに応じて、オペランド フィールドのビットを左または右に移動します。 すべてのシフト命令は、オペランドのコピー、shift_count という同じ構造を持っています。

シフトされるビット数 - counter_shifts - は、XNUMX 番目のオペランドの場所にあり、次の XNUMX つの方法で設定できます。

1) 直接オペランドを使用して固定値を設定する静的な方法。

2) 動的。これは、シフト命令を実行する前に、シフト カウンターの値を cl レジスターに入力することを意味します。

cl レジスタの次元に基づいて、シフト カウンタの値の範囲が 0 ~ 255 であることは明らかですが、実際には、これは完全に正しいわけではありません。 最適化のために、マイクロプロセッサはカウンタの最下位 0 ビットの値のみを受け入れます。つまり、値は 31 から XNUMX の範囲にあります。

すべてのシフト命令は、キャリー フラグを設定します。

ビットがオペランドからシフトアウトすると、最初にキャリーフラグにヒットし、オペランド外の次のビットの値に等しく設定されます。 このビットが次にどこに行くかは、シフト命令のタイプとプログラム アルゴリズムによって異なります。

シフト コマンドは、動作原理に従って XNUMX つのタイプに分けることができます。

1) 線形シフト コマンド。

2) サイクリック シフト コマンド。

線形シフト コマンド

このタイプのコマンドには、次のアルゴリズムに従ってシフトするコマンドが含まれます。

1) プッシュされる次のビットは、CF フラグを設定します。

2) もう一方の端からオペランドに入力されたビットの値は 0 です。

3) 次のビットがシフトされると、CF フラグに入りますが、前にシフトされたビットの値は失われます! 線形シフト コマンドは、次の XNUMX つのサブタイプに分けられます。

1) 論理線形シフト コマンド。

2) 算術線形シフト命令。

論理線形シフト コマンドには、次のものがあります。

1) shl オペランド、counter_shifts (論理左シフト) - 左への論理シフト。 オペランドの内容は、shift_count で指定されたビット数だけ左にシフトされます。 右側 (最下位ビットの位置) にゼロが入力されます。

2) shr オペランド、shift_count (論理右シフト) - 右への論理シフト。 オペランドの内容は、shift_count で指定されたビット数だけ右にシフトされます。 左側 (最上位の符号ビットの位置) にはゼロが入力されます。

図 30 は、これらのコマンドがどのように機能するかを示しています。

米。 30.線形論理シフトのコマンドの作業のスキーム

算術線形シフト命令は、特殊な方法でオペランドの符号ビットを操作するという点で、論理シフト命令とは異なります。

1) sal オペランド、shift_counter (算術左シフト) - 算術左シフト。 オペランドの内容は、shift_count で指定されたビット数だけ左にシフトされます。 右側 (最下位ビットの位置) にはゼロが入力されます。 sal 命令は符号を保持しませんが、次に進むビットによって符号が変更された場合に / でフラグを設定します。 それ以外は、sal コマンドは shl コマンドとまったく同じです。

2) sar オペランド、shift_count (算術右シフト) - 算術右シフト。 オペランドの内容は、shift_count で指定されたビット数だけ右にシフトされます。 左側のオペランドにゼロが挿入されます。 sar コマンドは符号を保持し、各ビット シフト後に符号を復元します。

図 31 は、線形算術シフト命令がどのように機能するかを示しています。

米。 31. 線形算術シフトコマンドの操作スキーム

回転コマンド

巡回シフト命令には、シフトされたビットの値を格納する命令が含まれます。 巡回シフト命令には次の XNUMX 種類があります。

1) 単純な巡回シフト コマンド。

2) キャリー フラグを介した巡回シフト コマンド。

単純な巡回シフト コマンドには次のものがあります。

1) rol オペランド、shift_counter (Rotate Left) - 左への巡回シフト。 オペランドの内容は、shift_count オペランドで指定されたビット数だけ左にシフトされます。 左にシフトされたビットは、同じオペランドに右から書き込まれます。

2) gog オペランド、counter_shifts (Rotate Right) - 右への巡回シフト。 オペランドの内容は、shift_count オペランドで指定されたビット数だけ右にシフトされます。 右シフトされたビットは、左側の同じオペランドに書き込まれます。

米。 32.単純な循環シフトのコマンドの操作のスキーム

図 32 からわかるように、単純な巡回シフトの命令は、作業の過程で XNUMX つの有用なアクションを実行します。つまり、巡回シフトされたビットが反対側からオペランドにプッシュされるだけでなく、同時にそのvalue は CE フラグの値になります。

キャリー フラグ CF による巡回シフト コマンドは、単純な巡回シフト コマンドとは異なります。シフトされたビットは、もう一方の端からオペランドにすぐには入力されず、最初にキャリー フラグ CE に書き込まれます。このシフト コマンドの次の実行のみ (ループで実行される場合)、前に進められたビットがオペランドのもう一方の端に配置されます (図 33)。

以下は、キャリー フラグを介した巡回シフト コマンドに関連しています。

1) rcl オペランド、shift_count (キャリー左回転) - キャリーによる巡回左シフト。

オペランドの内容は、shift_count オペランドで指定されたビット数だけ左にシフトされます。 シフトされたビットは、キャリ フラグ cf の値になります。

2)rsgオペランド、shift_count(Rotate through Carry Right)-キャリーによる右への巡回シフト。

オペランドの内容は、shift_count オペランドで指定されたビット数だけ右にシフトされます。 シフトされたビットは、キャリ フラグ CF の値になります。

米。 33. キャリー フラグ CF によるローテート命令

図33は、キャリーフラグを介してシフトするときに中間要素が現れることを示しています。これにより、特に循環的にシフトされたビット、特にビットシーケンスの不一致を置き換えることができます。

以下、ビットシーケンスのミスマッチとは、このシーケンスの必要なセクションを何らかの方法でローカライズして抽出し、それらを別の場所に書き込むことを可能にするアクションを意味します。

追加のシフト コマンド

i80386 以降の最新の Intel マイクロプロセッサ モデルのコマンド システムには、前述した機能を拡張する追加のシフト コマンドが含まれています。倍精度シフト コマンドは次のとおりです。

1) shld operand_1、operand_2、shift_counter - 倍精度左シフト。 shld コマンドは、図の図に従って、operand_1 のビットを左にシフトし、その右側のビットを operand_2 から置き換えられたビットの値で埋めることによって置換を実行します。 34. シフトされるビット数は、shift_counter 値によって決まります。値の範囲は 0 ~ 31 です。この値は、即値オペランドとして指定するか、cl レジスタに含めることができます。 operand_2 の値は変更されません。

米。 34. sld コマンドのスキーム

2) shrd operand_1、operand_2、shift_counter - 倍精度右シフト。この命令は、図 1 の図に従って、operand_2 オペランドのビットを右にシフトし、その左側のビットを operand_35 から置き換えられたビットの値で埋めることによって置換を実行します。シフトされるビットの数は次のとおりです。この値は、shift_counter の値によって決まり、0 ~ 31 の範囲にあります。この値は、即値オペランドによって指定することも、cl レジスタに含めることもできます。 operand_2 の値は変更されません。

米。 35. shrd コマンドのスキーム

前述のように、shld コマンドと shrd コマンドは最大 32 ビットまでシフトしますが、オペランドと演算アルゴリズムを指定する特殊性により、これらのコマンドを使用して最大 64 ビット長のフィールドを操作できます。

2. コントロール転送コマンド

プログラムの線形セクションが形成されるいくつかのコマンドについて知りました。 それらのそれぞれは、通常、何らかのデータ変換または転送を実行し、その後、マイクロプロセッサは制御を次の命令に転送します。 しかし、これほど一貫した方法で動作するプログラムはほとんどありません。 通常、プログラムには、次に実行する命令を決定しなければならないポイントがあります。 この解決策は次のとおりです。

1) 無条件 - この時点で、次に来るコマンドではなく、現在のコマンドから少し離れた別のコマンドに制御を移す必要があります。

2) 条件付き - 次に実行されるコマンドは、いくつかの条件またはデータの分析に基づいて決定されます。

プログラムは、一定量の RAM スペースを占有する一連のコマンドとデータです。 このメモリ空間は、連続しているか、複数のフラグメントで構成されている可能性があります。

次にどのプログラム命令を実行するか、マイクロプロセッサは cs の内容から学習します。 (e) ip レジスタ ペア:

1) cs - 現在のコード セグメントの物理 (ベース) アドレスを含むコード セグメント レジスタ。

2) eip/ip - 命令ポインタ レジスタ。現在のコード セグメントの先頭からの、次に実行される命令のメモリ内のオフセットを表す値が含まれます。

どの特定のレジスタが使用されるかは、設定​​されたアドレッシング モード use16 または use32 によって異なります。 use 16 が指定されている場合は ip が使用され、use32 が指定されている場合は eip が使用されます。

したがって、制御転送命令はcsおよびeip / ipレジスタの内容を変更します。その結果、マイクロプロセッサは次のプログラム命令ではなく、プログラムの他のセクションの命令を実行するために選択します。 マイクロプロセッサ内のパイプラインがリセットされます。

動作原理によれば、プログラム内の遷移を編成するマイクロプロセッサ コマンドは、次の 3 つのグループに分けることができます。

1. 制御コマンドの無条件転送:

1) 無条件分岐コマンド。

2) プロシージャを呼び出してプロシージャから戻るコマンド。

3) ソフトウェア割り込みを呼び出し、ソフトウェア割り込みから復帰するコマンド。

2. 制御の条件付き転送のコマンド:

1) 比較コマンド p の結果によるコマンドのジャンプ。

2) 特定のフラグの状態に応じてコマンドを遷移します。

3) esx/cx レジスタの内容をジャンプするための命令。

3. サイクル制御コマンド:

1) カウンタ ехх/сх でサイクルを編成するためのコマンド。

2) カウンター ех/сх を使用してサイクルを編成するためのコマンド。追加の条件によってサイクルから早期に終了する可能性があります。

無条件ジャンプ

前の説明で、移行メカニズムの詳細が明らかになりました。 ジャンプ命令は、eip/ip 命令ポインタ レジスタと、場合によっては cs コード セグメント レジスタを変更します。 正確に何を変更する必要があるかは、以下によって異なります。

1) 無条件分岐命令のオペランドのタイプ (near または far)。

2) ジャンプアドレスの前に修飾子を指定することから (ジャンプ命令で)。 この場合、ジャンプ アドレス自体は、命令内に直接配置することも (直接ジャンプ)、レジスタまたはメモリ セル内に配置することもできます (間接ジャンプ)。

修飾子は、次の値を取ることができます。

1) near ptr - 現在のコード セグメント内のラベルへの直接遷移。 コマンドで指定されたアドレス (ラベル) または値抽出記号 - $; を使用した式に基づいて、(指定された use16 または use32 コード セグメント タイプに応じて) eip/ip レジスタのみが変更されます。

2) far ptr - 別のコード セグメントのラベルへの直接遷移。 ジャンプ アドレスは、即値オペランドまたはアドレス (ラベル) として指定され、16 ビットのセレクターと 16/32 ビットのオフセットで構成され、それぞれ cs および ip/eip レジスターにロードされます。

3) word ptr - 現在のコード セグメント内のラベルへの間接遷移。 eip/ip のみが変更されます (コマンドで指定されたアドレスのメモリからのオフセット値、またはレジスタからのオフセット値によって)。 オフセット サイズ 16 または 32 ビット。

4) dword ptr - 別のコード セグメントのラベルへの間接遷移。 cs と eip / ip の両方のレジスタが変更されます (メモリからの値によって - メモリからのみ、レジスタから)。 このアドレスの最初のワード/dword はオフセットを表し、ip/eip にロードされます。 XNUMX 番目と XNUMX 番目の単語が cs に読み込まれます。 jmp 無条件ジャンプ命令

無条件ジャンプのコマンド構文は、jmp [modifier] jump_address - リターン ポイントに関する情報を保存しない無条件ジャンプです。

Jump_address は、ラベル形式のアドレス、またはジャンプ ポインタが配置されているメモリ領域のアドレスです。

全体として、マイクロプロセッサ命令システムには、無条件ジャンプ jmp 用の機械語命令のコードがいくつかあります。

それらの違いは、遷移距離とターゲット アドレスの指定方法によって決まります。 ジャンプ距離は jump_address オペランドの位置によって決まります。 このアドレスは、現在のコード セグメントまたは他のセグメントにある可能性があります。 最初のケースでは、トランジションはセグメント内または近いと呼ばれ、XNUMX番目のセグメントではセグメント間または遠いと呼ばれます。 セグメント内ジャンプは、eip/ip レジスタの内容のみが変更されることを前提としています。

jmp コマンドをセグメント内で使用するには、次の XNUMX つのオプションがあります。

1) ストレートショート;

2) ストレート;

3) 間接的。

手続き

アセンブリ言語には、コードのセクションの重複の問題を解決するツールがいくつかあります。 これらには以下が含まれます:

1) 手続きの仕組み;

2) マクロアセンブラ;

3) 割り込みメカニズム。

サブルーチンとも呼ばれるプロシージャは、タスクを分解 (いくつかの部分に分割) するための基本的な機能単位です。 プロシージャは、特定のサブタスクを解決するためのコマンドのグループであり、より高いレベルでタスクが呼び出されたポイントから制御を受け取り、このポイントに制御を戻す手段を備えています。

最も単純なケースでは、プログラムは単一のプロシージャーで構成されます。 言い換えれば、手続きは整形式の一連のコマンドとして定義でき、一度記述すれば、必要に応じてプログラム内のどこからでも呼び出すことができます。

一連のコマンドをアセンブリ言語のプロシージャとして記述するには、PROC と ENDP の XNUMX つのディレクティブが使用されます。

手続き記述構文は以下の通りです(図36)。

米。 36. プログラム中の手続き記述の構文

図 36 は、プロシージャ ヘッダー (PROC ディレクティブ) で、プロシージャ名のみが必須であることを示しています。 PROC ディレクティブの多数のオペランドの中で、[距離] を強調する必要があります。 この属性は、near または far の値を取ることができ、別のコード セグメントからプロシージャを呼び出す可能性を特徴付けます。 デフォルトでは、[distance] 属性は near に設定されています。

プロシージャーは、プログラムのどこにでも配置できますが、ランダムに制御を取得しないようにします。 手順が単に一般的な命令ストリームに挿入された場合、マイクロプロセッサは手順の命令をこのストリームの一部として認識し、したがって、手順の命令を実行します。

条件付きジャンプ

マイクロプロセッサには、18 個の条件付きジャンプ命令があります。 これらのコマンドを使用すると、次のことを確認できます。

1) 符号付きのオペランド間の関係 (「大きい - 小さい」)。

2) 符号なしのオペランド間の関係 (「上位 - 下位」)。

3) 算術フラグ ZF、SF、CF、OF、PF の状態 (ただし、AF は除く)。

条件付きジャンプ コマンドの構文は同じです。

jcc jump_label

ご覧のとおり、すべてのコマンドのニーモニック コードは "j" で始まります。単語 jump (ジャンプ) から、コマンドによって分析される特定の条件を決定します。

jump_label オペランドに関しては、このラベルは現在のコード セグメント内にのみ配置できます。条件付きジャンプでのセグメント間の制御の転送は許可されていません。 この点で、無条件ジャンプ コマンドの構文に存在する修飾子については疑問の余地がありません。 マイクロプロセッサの初期のモデル (i8086、i80186、および i80286) では、条件分岐命令は、条件分岐命令に続く命令から -128 バイトから +127 バイトまでの短いジャンプしか実行できませんでした。 マイクロプロセッサ モデル 80386 以降では、この制限は削除されていますが、ご覧のとおり、現在のコード セグメント内のみです。

制御が条件付きジャンプ コマンドに転送される場所を決定するには、最初に条件を形成する必要があります。これに基づいて、制御を転送する決定が行われます。

このような状態の原因は次のとおりです。

1) 算術フラグの状態を変更するコマンド。

2) XNUMX つのオペランドの値を比較する比較命令 p。

3) esx/cx レジスタの状態。

cmp 比較コマンド

ページ比較コマンドには興味深い働き方があります。 これは、減算コマンドのサブオペランド、operand_2 とまったく同じです。

p 命令は、sub 命令と同様に、オペランドを減算し、フラグを設定します。 行わない唯一のことは、最初のオペランドの代わりに減算の結果を書き込むことです。

コマンド構文 str - str operand_1, operand_2 (compare) - XNUMX つのオペランドを比較し、比較結果に基づいてフラグを設定します。

p コマンドによって設定されたフラグは、特別な条件分岐命令によって分析できます。 それらを見る前に、これらの条件付きジャンプ命令のニーモニックに少し注意を払いましょう (表 16)。 条件付きジャンプコマンドの名称(jccコマンドの名称に含まれる要素、当社が指定)を形成する際の表記法を理解すると、覚えやすくなり、さらに実用的になります。

表 16. jcc コマンド名の略語の意味 表 17. コマンド p operand_1、operand_2 の条件付きジャンプ コマンドのリスト

条件分岐コマンドのいくつかの異なるニーモニック コードが同じフラグ値に対応しているという事実に驚かないでください (表 17 では、それらはスラッシュで区切られています)。 名前の違いは、条件付きジャンプ命令を特定の命令グループと組み合わせて使用​​しやすくしたいというマイクロプロセッサ開発者の要望によるものです。 したがって、名前が異なれば機能の方向性も異なります。 ただし、これらのコマンドが同じフラグに応答するという事実は、プログラム内でそれらを完全に同等かつ同等にします。 したがって、表 17 では、名前ではなく、応答するフラグ (条件) の値によってグループ化されています。

条件分岐命令とフラグ

一部の条件付きジャンプ命令のニーモニック指定は、それらが動作するフラグの名前を反映しており、次の構造を持っています。最初の文字は「j」(ジャンプ、ジャンプ)、1 番目の文字はフラグ指定または否定文字「」です。 n" の後にフラグの名前が続きます。 このチーム構造は、その目的を反映しています。 文字「n」がなければフラグの状態をチェックし、0であればジャンプラベルに遷移する。 文字「n」が存在する場合、フラグの状態が XNUMX に等しいかどうかがチェックされ、成功した場合は、ジャンプ ラベルへのジャンプが行われます。

コマンド ニーモニック、フラグ名、およびジャンプ条件を表 18 に示します。これらのコマンドは、指定されたフラグを変更する任意のコマンドの後に使用できます。

表 18. 条件付きジャンプ命令とフラグ

表 17 と 18 をよく見ると、両方とも同じフラグの分析に基づいているため、それらの条件付きジャンプ命令の多くが同等であることがわかります。

条件付きジャンプ命令と esx/cx レジスタ

マイクロプロセッサのアーキテクチャには、多くのレジスタの特定の使用が含まれます。 たとえば、EAX / AX / AL レジスタはアキュムレータとして使用され、BP、SP レジスタはスタックを操作するために使用されます。 ECX / CX レジスタには、特定の機能上の目的もあります。ループ制御コマンドや文字列を操作するときにカウンターとして機能します。 機能的には、esx/cx レジスタに関連付けられた条件付き分岐命令が、この命令グループにより正確に割り当てられる可能性があります。

この条件分岐命令の構文は次のとおりです。

1) jcxz jump_label (ex がゼロの場合ジャンプ) - cx がゼロの場合ジャンプ;

2) jecxz jump_label (Jump Equal ех Zero) - ех がゼロの場合にジャンプします。

これらのコマンドは、ループするときや文字列を操作するときに非常に便利です。

jcxz/jecxz コマンドには固有の制限があることに注意してください。 他の条件付き転送命令とは異なり、jcxz/jecxz 命令は、それに続く命令から -128 バイトまたは +127 バイトのショート ジャンプのみをアドレス指定できます。

サイクルの編成

ご存知のように、サイクルは重要なアルゴリズム構造であり、これを使用しないと、おそらくどのプログラムも実行できません。 たとえば、制御コマンドの条件付き転送または無条件ジャンプ コマンド jmp を使用して、プログラムの特定のセクションの周期的な実行を編成できます。 このようなサイクル組織では、その組織のすべての操作が手動で実行されます。 しかし、サイクルなどのアルゴリズム要素の重要性を考慮して、マイクロプロセッサの開発者は、サイクルのプログラミングを容易にするXNUMXつのコマンドのグループを命令システムに導入しました。 これらの命令は、esx/cx レジスタをループ カウンターとしても使用します。

これらのコマンドについて簡単に説明します。

1) ループtransition_label (ループ) - サイクルを繰り返します。このコマンドを使用すると、ループ カウンターの自動デクリメントを使用して、高級言語の for ループと同様のループを編成できます。チームの仕事は次のことを行うことです。

a) ECX/CX レジスタのデクリメント;

b) ECX/CX レジスタをゼロと比較する: (ECX/CX) = 0 の場合、制御はループ後の次のコマンドに移ります。

2) loope/loopz jump_label

loope コマンドと loopz コマンドは完全に同義です。 コマンドの作業は、次のアクションを実行することです。

a) ECX/CX レジスタのデクリメント;

b) ECX/CX レジスタをゼロと比較する。

c) ゼロ フラグ ZF のステータスの分析 (ECX/CX) = 0 または XF = 0 の場合、制御はループの後に次のコマンドに転送されます。

3) loopne/loopnz jump_label

コマンド loopne と loopnz も絶対的な同義語です。 コマンドの作業は、次のアクションを実行することです。

a) ECX/CX レジスタのデクリメント;

b) ECX/CX レジスタをゼロと比較する。

c) ゼロフラグ ZF の状態の分析: (ECX/CX) = 0 または ZF = 1 の場合、制御はループの後に次のコマンドに移されます。

loope/loopz コマンドと loopne/loopnz コマンドは相互に作用します。 これらは、zf フラグを追加で解析することにより、ループ コマンドのアクションを拡張します。これにより、このフラグをインジケーターとして使用して、ループから早期に終了することができます。

ループ コマンド loop、loope/loopz、および loopne/loopnz の欠点は、短いジャンプ (-128 から +127 バイト) しか実装しないことです。 長いループを処理するには、条件付きジャンプと jmp 命令を使用する必要があるため、ループを編成する両方の方法を習得するようにしてください。

著者: Tsvetkova A.V.

面白い記事をお勧めします セクション 講義ノート、虎の巻:

管理。 ベビーベッド

社会心理学。 ベビーベッド

簡単に言えば、XNUMX世紀のロシア文学。 ベビーベッド

他の記事も見る セクション 講義ノート、虎の巻.

読み書き 有用な この記事へのコメント.

<<戻る

科学技術の最新ニュース、新しい電子機器:

タッチエミュレーション用人工皮革 15.04.2024

距離を置くことがますます一般的になっている現代のテクノロジーの世界では、つながりと親近感を維持することが重要です。ドイツのザールランド大学の科学者らによる人工皮膚の最近の開発は、仮想インタラクションの新時代を象徴しています。ドイツのザールラント大学の研究者は、触覚を遠くまで伝えることができる超薄膜を開発した。この最先端のテクノロジーは、特に愛する人から遠く離れている人たちに、仮想コミュニケーションの新たな機会を提供します。研究者らが開発した厚さわずか50マイクロメートルの極薄フィルムは、繊維に組み込んで第二の皮膚のように着用することができる。これらのフィルムは、ママやパパからの触覚信号を認識するセンサーとして、またその動きを赤ちゃんに伝えるアクチュエーターとして機能します。保護者が布地に触れるとセンサーが作動し、圧力に反応して超薄膜を変形させます。これ ... >>

Petgugu グローバル猫砂 15.04.2024

ペットの世話は、特に家を清潔に保つことに関しては、しばしば困難になることがあります。 Petgugu Global のスタートアップ企業から、猫の飼い主の生活を楽にし、家を完璧に清潔で整頓された状態に保つのに役立つ、新しい興味深いソリューションが発表されました。スタートアップの Petgugu Global は、糞便を自動的に流し、家を清潔で新鮮に保つことができるユニークな猫用トイレを発表しました。この革新的なデバイスには、ペットのトイレ活動を監視し、使用後に自動的に掃除するように作動するさまざまなスマートセンサーが装備されています。この装置は下水道システムに接続されており、所有者の介入を必要とせずに効率的な廃棄物の除去を保証します。また、トイレには大容量の水洗トイレがあり、多頭飼いのご家庭にも最適です。 Petgugu 猫砂ボウルは、水溶性猫砂用に設計されており、さまざまな追加機能を提供します。 ... >>

思いやりのある男性の魅力 14.04.2024

女性は「悪い男」を好むという固定観念は長い間広まっていました。しかし、モナシュ大学の英国の科学者によって行われた最近の研究は、この問題について新たな視点を提供しています。彼らは、男性の感情的責任と他人を助けようとする意欲に女性がどのように反応するかを調べました。この研究結果は、男性が女性にとって魅力的な理由についての私たちの理解を変える可能性がある。モナシュ大学の科学者が行った研究により、女性に対する男性の魅力に関する新たな発見がもたらされました。実験では、女性たちに男性の写真と、ホームレスと遭遇したときの反応など、さまざまな状況での行動についての簡単なストーリーを見せた。ホームレス男性を無視する人もいたが、食べ物をおごるなど手助けする人もいた。ある研究によると、共感と優しさを示す男性は、共感と優しさを示す男性に比べて、女性にとってより魅力的であることがわかりました。 ... >>

アーカイブからのランダムなニュース

メタバースはソーシャル ネットワークよりも悪い可能性があります 01.12.2021

現代のテクノロジーは仮想現実と拡張現実に移行しており、メタバースに対応するためのスペースを生み出しています。 懐疑論者はこの傾向に否定的であり、メタバースが人々に知られている現実の終わりにつながる可能性があると信じています. 最初の拡張現実システムの発明者であるルイス・ローゼンバーグが示唆するように、メタバースはソーシャル ネットワークよりもはるかに悪い可能性があります。

拡張現実とメタバースは、可能な限り最も自然な方法でコンテンツを提示することを目指しており、それによって「現実感を変え」、人々の心の境界を取り除き、日常の経験の解釈を歪めています.

「個人的には、それは私を怖がらせます。拡張現実は社会のあらゆる側面を根本的に変えますが、必ずしも良い方向に向かうとは限りません」と Rosenberg 氏は説明しました。

専門家によると、ソーシャル ネットワークは、ユーザーに表示されるコンテンツをフィルタリングすることで現実を操作します。 人々は、人々と日常生活の間に無数のテクノロジー層を提供および維持するために、ますます企業に依存するようになっています。

ローゼンバーグが信じているように、拡張現実は生活の不可欠な部分となり、人々は単純に拡張現実メガネを外して実際の問題に対処することができなくなります. ポイントを取り除くということは、その人が社会的、経済的、知的立場で不利になることを意味します。 Rosenberg 氏は、拡張現実は簡単に社会を分裂させ、人々の間に不和の種をまくことができるため、すべての人に注意するよう促しています。

その他の興味深いニュース:

▪ 低ノイズ 38V LDO レギュレータ ST Microelectronics LDO40L

▪ ポリエチレンに対する蜂の蛾

▪ 2 ポート PCIe 3.0 コンバージド バス アダプター

▪ 磁場がグラフェンに異常な影響を与える

▪ 地球の軌道は衛星管理人によってきれいにされます

科学技術、新しいエレクトロニクスのニュースフィード

 

無料の技術ライブラリの興味深い資料:

▪ 電気技師の Web サイトのセクション。 PTE. 記事の選択

▪ ウォルター・ベンジャミンによる記事。 有名な格言

▪ 記事 毒蛇とは何ですか? 詳細な回答

▪ 記事のクルーノット。 旅行のヒント

▪ 記事 デジタル蛍光体オシロスコープ。 無線エレクトロニクスと電気工学の百科事典

▪ 記事 最大 1 kV の電圧の架空送電線。 序文。 無線エレクトロニクスと電気工学の百科事典

この記事にコメントを残してください:

Имя:


Eメール(オプション):


コメント:





このページのすべての言語

ホームページ | 図書館 | 物品 | サイトマップ | サイトレビュー

www.diagram.com.ua

www.diagram.com.ua
2000-2024