インデックスレジスタとは|仕組み・利点・x86/ARM/6502別実装とコンパイラ最適化ガイド

インデックスレジスタとは — 概要

インデックスレジスタ(index register)は、CPUがメモリアドレスを計算する際に用いるレジスタの一種で、配列要素やテーブル参照など連続したデータ構造の走査・アクセスを効率化するために使われます。高級言語での配列インデックスやポインタ算術に相当するアドレスオフセットを保持し、命令実行時にベースアドレスに加算(あるいはスケールして加算)して実効アドレスを得るために利用されます。

なぜ必要か — 利点と役割

  • アドレス計算の簡潔化:配列やテーブルアクセスは「基準アドレス + 要素サイズ×インデックス」の形を取ることが多く、インデックスレジスタを用いればこの加算を効率的に実行できます。

  • 命令数の削減:アドレス計算を専用レジスタやアドレッシングモードに任せられるため、ループ内で不要な算術命令を削減でき、コードが短く高速になります。

  • ハードウェアによる最適化:スケーリング(index×1/2/4/8 など)や自動インクリメント/デクリメントなど、アドレス計算をハードウェア化することで命令パイプラインの効率が上がります。

基本的な仕組み

典型的な使い方は、命令が「ベースレジスタ(base) + インデックスレジスタ(index) × スケール(scale) + ディスプレースメント(disp)」という形式で実効アドレスを生成するものです。スケールは要素サイズ(バイト数)に対応し、インデックスを乗算することで配列要素のオフセットを得ます。これにより、たとえば 32ビット整数配列 a[i] をロードする命令は base=a, index=i, scale=4 の組合せで一命令で表せます。

アーキテクチャごとの実装例

  • 8ビット系(例:6502)
    MOS 6502では X, Y の2つのインデックスレジスタが存在し、命令レベルで「命令オペランドのアドレスに X/Y を加える」形式(例:LDA $2000,X)をサポートします。シンプルなアドレッシングで組み込み機器やゲーム機向けに軽量なループ処理が可能です。

  • x86 系(8086〜32/64ビット)
    8086系では SI(Source Index)、DI(Destination Index)などが文字列操作やインデックス用に慣習的に使われます。80386以降の32/64ビットモードでは、一般レジスタ(EAX, EBX, ECX, EDX, RSI, RDI 等)を任意に「base + index*scale + disp」形式で用いることができ、SIB(Scale-Index-Base)バイトにより scale (1,2,4,8) を指定できます。これにより多様なアドレス形成が一命令で可能です。

  • ARM 系
    ARMはロード/ストア命令でベースレジスタ+(オプションで)レジスタオフセット(左シフトなどによるスケーリングを含む)をサポートします。さらに事前/事後インデクシング(pre/post-index)により、ロード時に自動でインクリメント/デクリメントする機能も提供され、高速なポインタ走査が可能です。

  • RISC(例:MIPS、RISC-V)
    多くのRISC設計ではロード/ストアがベースレジスタ+即値オフセットの形式で単純化されます。インデックスのスケーリングはコンパイラがシフト命令で前もって計算し、ベースに加算する形で実現します。命令セット自体は単純ですが、レジスタを組合せることで同等の機能を実現します。

  • 歴史的(例:PDP-11, VAX)
    PDP-11やVAXなどの古典的CISC機は「自動インクリメント/デクリメント」や豊富な間接アドレッシングモードを持ち、レジスタをアドレス計算へ直接組み込む多様な手段を提供していました(例:MOV (R1)+, R2 のような自動更新)。

具体例(アセンブリ)

以下は各架空あるいは一般的な構文による例示です。

  • x86(IA-32)
    mov eax, [ebx + ecx*4 + 8]
    これは EAX に、EBX(配列のベース)+ECX×4(インデックス×要素サイズ)+8(定数オフセット)で示されるメモリをロードします。

  • 6502
    LDA $2000,X
    アドレス $2000 に X レジスタの値を加えた位置から A レジスタにロードします(8ビット環境の典型)。

  • ARM(ARM アセンブリ)
    LDR r0, [r1, r2, LSL #2]
    r1(ベース)に r2 を左シフト2(×4)した値を加えた位置から r0 にロードします。

インデックスレジスタとポインタの違い

「インデックスレジスタ」は主にアドレス計算用に使うのに対し、「ポインタ」はデータの実体を直接指すアドレスを保持するために使われることが多いという運用上の差があります。とはいえ、実際のCPUでは同じ物理レジスタをどちらの用途にも使えることが一般的で、区別はアーキテクチャや呼称の違いに依存します。

自動インクリメント/デクリメントとその利点

一部の命令セットでは、メモリアクセス命令が実行されると同時にインデックスレジスタに自動的に増減を適用する機能があります(例:post-increment)。これによりループでの反復処理が1命令で済み、コード密度と実行効率が向上します。ただし現代の深いパイプラインやアウトオブオーダ実行の環境では、単純に命令数が減ることが必ずしも性能向上に直結しないこともあります。

コンパイラと高級言語での利用

CやC++などの言語で配列 a[i] のような参照は、最終的に「配列のベースアドレス + i × sizeof(element)」というアドレス計算にコンパイルされます。コンパイラはループ最適化やレジスタ割付(register allocation)を行い、インデックスやベースを適切にレジスタに割り当てて、できるだけアドレス計算やメモリアクセスを効率化します。ベクトル化やループアンローリング時には複数のインデックス値を同時に扱うこともあります。

セキュリティと注意点

インデックス計算はバッファオーバーフローなどの原因になり得ます。高級言語での境界チェックを怠ると不正なメモリアクセスを招き、脆弱性になります。また、アドレス計算が枝分岐や不定値に依存するとパフォーマンスが低下するため、コンパイラや開発者はループの単純化や境界チェックの最適化に注意を払います。ハードウェアレベルでは、近年はメモリ保護(MMU、分離化)やソフトウェアによるチェック(コンパイラ挿入、静的解析)が重要です。

現代CPUにおける位置づけ

かつては「専用のインデックスレジスタ」がアーキテクチャの特徴でしたが、現代のCISC/RISCいずれにおいても多くは汎用レジスタを利用してインデックス的な役割を実現します。重要なのは「アドレス形成をどの程度命令セットでサポートしているか」であり、スケールや自動更新、複雑なベース+インデックス+ディスプレースメントのサポートが命令密度やコード効率、最終的な性能に影響します。

まとめ

インデックスレジスタは、配列やテーブルの走査、ポインタ操作、ループ処理などで効率的なアドレス計算を可能にする重要な概念です。古典的な専用レジスタから、現代の汎用レジスタと高度なアドレッシングモードまで、その実装形式は多様ですが、目的は常に「効率的で柔軟なメモリアクセスの実現」にあります。アーキテクチャの理解は、コンパイラ最適化や低レベルパフォーマンスチューニング、さらにはセキュリティ観点からも有用です。

参考文献