web-dev-qa-db-ja.com

コンパイラーでの非固定長配列サポートの実装

PICマイコン用の言語を作ろうと思っています。次のように、非固定サイズの配列を使用できるようにしたいと思います。

  1. 変数をint[]として宣言します
  2. シリアル接続からの入力を待つ
  3. 変数をinput長くする

このような機能は便利だと思いますが、Assemblyにコンパイルするコンパイラでこれを実装する方法を知りません。もちろん、配列を連続したレジスタアドレスに格納したいのですが。私はPICを使用しているので、これはメモリ効率が非常に高くなければなりません。

誰かがint[]を書き込んだとき、変数用にメモリ領域を予約することはまだ良い考えではないと思いますか?その場合、配列の最大サイズは固定されます。たとえば、配列が予約済みメモリアドレス5-100を取得し、他の変数が4および101を取得する場合、配列には固定境界があり、96レジスタを超えることはできません。また、最初からメモリを予約する場合、xバイトとしましょう。結局、必要なのはyバイトだけなので、無駄に消費しますx-yバイト。それは欲しくない。

つまり、私が目にする唯一のオプションは、その場でアレイを初期化し、マイクロコントローラーのスペースを予約することです。もちろん、これにはある程度のメモリと実行時間がかかります。私はこのようなシステムを考えました:

  • 配列の初期化int[x] = {int, int}は、最初から初期化されていない配列の開始と終了へのポインタを保持します-xは配列の最大数になります(これは譲歩ですが、すべての配列の最大長よりも優れています)
  • 使用する配列の数を示す変数c = 0を格納します
  • 初期化された(予約された)メモリの境界をどこかの変数に格納する
  • 配列が長さになるとき:

    • インデックスの最初の点から配列の開始(現在の境界)と終了(現在の境界+長さ)へのポインターを置きますc
    • 増分c

私はこれでうまくいくと思います(そうでしょうか?)が、主にメモリに関するいくつかの短所があります。配列cと現在のメモリ境界をオーバーヘッドとして格納する必要があります。

PICマイクロコントローラーの言語で非固定サイズの配列を実装するより良い方法はありますか?私の要件は:

  • 低メモリオーバーヘッド
  • 配列の長さはnotをオンザフライで変更する必要があります
  • 私が考えたシステムでは、まだ初期化されていない配列に値を格納することはできません。未定義の長さの配列に値を格納できるシステムがある場合、それは利点になります
  • より高速なシステム(実行時、コンパイル時間は問題ではない)が望ましい
6
user76821

サイズがわかっているポイントまで可変長配列の宣言を遅らせることを許可すれば、おそらくそれがはるかに簡単になり、多くのユーザーエラーを回避できます。次に、配列サイズを割り当てるための構文を作成する必要はありません。また、配列が宣言される順序と配列がサイズを取得する順序の違いに対処する必要はありません。

コンパイラーがローカル変数にスタックベースの割り当てスキームを使用している場合(「ビッグ」プラットフォームで行われるように、関数のスタックフレームに関連するアドレスが与えられます)、可変長配列を作成することができます必要に応じてスタックフレームの。関数に複数の可変長配列がある場合のみ、それぞれのデータの開始位置を示す追加のポインターが必要になります。

コンパイラーが固定割り当てスキームを使用する場合(ローカルを含むすべての変数に、コンパイラー/リンカーによって固定(絶対)アドレスが与えられます)、可変長配列を分割するために「配列スタック」を使用します。オーバーヘッドとして、現在空き領域がどこから始まるかを示すグローバルポインター、データが配置されている場所を示す各可変長配列のポインター、および可変長配列を使用する各関数でグローバルを復元する方法が必要です。関数に入るときに持っていた値へのポインタ。
ユーザーは、コンパイラー/リンカーに、必要と考える「配列スタック」の量を示す方法も必要です。妥当なデフォルトは、すべての固定サイズの変数と可変長配列のオーバーヘッドを割り当てた後に残っているものを使用することです。

このスキームは、スペースと時間の両方で、オーバーヘッドの量が最も少ないと思います。あなたが思いついたスキームは基本的にmalloc実装です。

C99の可変長配列は依然として固定サイズです。実行時までサイズがわからないだけです。これらは動的ではないため、int foo[]を宣言して要素へのアクセスを開始することはできません。それに割り当てるスペースの量を知る方法はなく、配列が大きくなるにつれてすべてを再配置する舞台裏で行われる魔法はありません。

以前のバージョンのCでは、すべての変数が前もって宣言されていたため、コンパイラーはスコープの開始時にスタックポインターを1回移動しました。

{
  int foo;
  int bar;
  // Stack pointer gets nudged 2 * sizeof(int)
  ... Code ...
}

VLAを可能にしたのは、C99の変更で、スコープの最上位ではなく、コードの任意の場所で変数を宣言できるようになりました。

{
  int foo;  // Stack pointer gets nudged sizeof(int)
   ... Code ...
  int bar;  // Stack pointer gets nudged another sizeof(int)
}

これを行うことができるということは、VLAを作成するために、括弧内の式を評価するだけです(コンパイラはそれらを見つける場所を知っているため、以前に宣言された変数を使用できます)。結果に、配列タイプとスタックポインターをその分だけ微調整します。

{
  size_t size;  // Stack pointer gets nudged sizeof(size_t)
  size = get_number_of_ints_in_input();
   ... Code ...
  int data[size];  // Stack pointer gets nudged size * sizeof(int)
   ... Code ...
}

スタックポインターがどれだけ移動したかを追跡している限り、スコープ外に出たときはいつでも、それを元の位置に戻すことができます。

2
Blrfl