Cでのモジュラーファームウェア設計用のメモリ割り当ての可能性


16

モジュラーアプローチは一般的に非常に便利(ポータブルでクリーン)であるため、可能な限り他のモジュールから独立したモジュールをプログラムするようにします。私のアプローチのほとんどは、モジュール自体を記述する構造体に基づいています。初期化関数は主なパラメーターを設定し、その後、ハンドラー(desriptive構造体へのポインター)がモジュール内の任意の関数に渡されます。

今、私はモジュールを記述する構造体のためのメモリの割り当ての最良のアプローチが何であるか疑問に思っています。可能であれば、私は次のものが欲しいです:

  • 不透明な構造体。したがって、構造体は、提供されたインターフェイス関数を使用することによってのみ変更できます。
  • 複数のインスタンス
  • リンカによって割り当てられたメモリ

次の可能性があり、それはすべて私の目標の1つと矛盾しています。

グローバル宣言

複数のインスタンス、リンカーによって割り当てられますが、構造体は不透明ではありません

(#includes)
module_struct module;

void main(){
   module_init(&module);
}

malloc

不透明な構造体、複数のインスタンス、ただしヒープ上の割り当て

module.h内:

typedef module_struct Module;

module.c init関数、mallocおよび割り当てられたメモリへの戻りポインタ

module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;

main.c内

(#includes)
Module *module;

void main(){
    module = module_init();
}

モジュール内の宣言

リンカによって割り当てられた不透明な構造体、事前定義された数のインスタンスのみ

構造体とメモリ全体をモジュールの内部に保持し、ハンドラーまたは構造体を公開しないでください。

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

ヒープの割り当てと複数/任意の数のインスタンスの代わりに、不透明な構造体、リンカーのためにこれらを何らかの形で組み合わせるオプションはありますか?

解決

以下のいくつかの回答で提案されているように、最善の方法は次のとおりだと思います:

  1. モジュールソースファイル内のMODULE_MAX_INSTANCE_COUNTモジュール用のスペースを予約
  2. モジュール自体にMODULE_MAX_INSTANCE_COUNTを定義しないでください
  3. モジュールヘッダーファイルに#ifndef MODULE_MAX_INSTANCE_COUNT #errorを追加して、モジュールユーザーがこの制限を認識し、アプリケーションに必要なインスタンスの最大数を定義するようにします
  4. インスタンスの初期化時に、記述的構造体のメモリアドレス(* void)またはモジュールインデックス(好きなもの)を返します。

12
ほとんどの組み込みFW設計者は、メモリ使用量を決定的かつシンプルに保つために、動的割り当てを避けています。特に、ベアメタルであり、メモリを管理するための基盤となるOSがない場合。
ユージーンSh。

まさに、それがリンカに割り当てを行わせる理由です。
L.ハインリッヒ

4
理解できるかどうかはよくわかりません...動的な数のインスタンスがある場合、どのようにしてリンカによってメモリを割り当てることができますか?それは私にはかなり直交しているようです。
jcaron

リンカに1つの大きなメモリプールを割り当てさせ、そこから独自の割り当てを行わせてください。これにより、オーバーヘッドがゼロのアロケータの利点も得られます。割り当て関数を使用して、プールオブジェクトをファイルに対して静的にして、プライベートにすることができます。一部のコードでは、さまざまなinitルーチンですべての割り当てを行い、その後、割り当てられた量を出力するため、最終的なプロダクションコンパイルでは、プールをその正確なサイズに設定しました。
リーダニエルクロッカー

2
コンパイル時の決定であれば、Makefileまたは同等のもので番号を定義するだけで十分です。番号はモジュールのソースにはありませんが、アプリケーション固有のものであり、インスタンス番号をパラメーターとして使用するだけです。
jcaron

回答:


4

ヒープの割り当てと複数/任意の数のインスタンスの代わりに、匿名の構造体、リンカーのためにこれらを何らかの形で組み合わせるオプションはありますか?

確かにあります。ただし、まず、コンパイル時に「任意の数」のインスタンスを修正するか、少なくとも上限を設定する必要があることを認識してください。これは、インスタンスが静的に割り当てられるための前提条件です(「リンカー割り当て」と呼んでいます)。それを指定するマクロを宣言することにより、ソースを変更せずに数値を調整可能にすることができます。

次に、実際の構造体宣言とそれに関連するすべての関数を含むソースファイルは、内部リンケージを持つインスタンスの配列も宣言します。これは、インスタンスへのポインタの外部リンケージを持つ配列、またはインデックスによってさまざまなポインタにアクセスするための関数を提供します。関数のバリエーションはもう少しモジュール化されています:

module.c

#include <module.h>

// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif

struct module {
    int demo;
};

// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];

// module functions

struct module *module_init(unsigned index) {
    instances[index].demo = 42;
    return &instances[index];
}

ヘッダーが構造体を不完全な型として宣言し、すべての関数(その型ポインターの観点から記述されいる)を宣言する方法については既にご存じだと思います。例えば:

module.h

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

現在、*struct module以外の変換単位では不透明でありmodule.c、動的割り当てなしでコンパイル時に定義されたインスタンスの数までアクセスして使用できます。


*もちろん、その定義をコピーしない限り。ポイントはそれをmodule.hしないということです。


クラス外からインデックスを渡すのは奇妙なデザインだと思います。このようなメモリプールを実装する場合、インデックスをプライベートカウンタにし、割り当てられたインスタンスごとに1ずつ増やします。「NUM_MODULE_INSTANCES」に達するまで、コンストラクタはメモリ不足エラーを返します。
ランディン

@Lundin、それは公平なポイントです。設計のその側面では、インデックスに固有の重要性があると想定していますが、実際にはそうでない場合もあります。それはある OPの開始場合のために、自明そういえ、ケース。そのような重要性は、存在する場合、インスタンスポインターを取得する手段を提供することでさらにサポートできます。初期化せずにます。
ジョンボリンジャー

したがって、基本的には、使用するモジュールの数に関係なく、n個のモジュール用にメモリを予約し、アプリケーションが初期化する場合、次の未使用要素へのポインタを返します。それはうまくいくと思います。
L.ハインリッヒ

@ L.Heinrichsはい、組み込みシステムは決定論的な性質を持つためです。「無限の量のオブジェクト」や「未知の量のオブジェクト」といったものはありません。多くの場合、オブジェクトもシングルトン(ハードウェアドライバー)であるため、オブジェクトのインスタンスが1つだけ存在するため、メモリプールは必要ありません。
ランディン

ほとんどの場合に同意します。質問には、いくつかの理論的な関心もありました。ただし、十分なIOが利用可能な場合は、数百の1線温度センサーを使用できます(1つの例として、今思いつくことができます)。
L.ハインリッヒ

22

私はC ++で小さなマイクロコントローラーをプログラミングします。

モジュールと呼ぶものはC ++クラスであり、データ(外部からアクセス可能かどうか)と関数(同様に)を含むことができます。コンストラクター(専用関数)がそれを初期化します。コンストラクターは、実行時パラメーターまたは(私のお気に入りの)コンパイル時(テンプレート)パラメーターを使用できます。クラス内の関数は、クラス変数を最初のパラメーターとして暗黙的に取得します。(または、しばしば私の好みでは、クラスは隠されたシングルトンとして機能できるため、このオーバーヘッドなしですべてのデータにアクセスできます)。

クラスオブジェクトは、グローバル(リンク時にすべてが適合することを知っている)、またはおそらくメインのスタックローカルになります。(未定義のグローバル初期化順序のために、C ++グローバルは好きではないので、スタックローカルを好みます)。

私が好むプログラミングスタイルは、モジュールが静的クラスであり、その(静的)構成がテンプレートパラメータによるものであることです。これにより、ほぼすべてのオーバーハッドが回避され、最適化が可能になります。これをスタックサイズを計算するツールと組み合わせると、心配なくスリープできます:)

C ++でのこのコーディング方法に関する私の講演:オブジェクト?結構です!

多くの組み込み/マイクロコントローラプログラマは、C ++のすべてを使用せざるを得ないと考えるため、C ++が嫌いなようです。これは絶対に必要なことではなく、非常に悪い考えです。(おそらくCのすべてを使用するわけではありません!ヒープ、浮動小数点、setjmp / longjmp、printfなどを考えてください...)


コメントで、Adam HaunはRAIIと初期化について言及しています。IMO RAIIは解体と関係がありますが、彼のポイントは有効です。グローバルオブジェクトはメインの開始前に構築されるため、無効な仮定(メインクロック速度が後で変更されるなど)で動作する可能性があります。これは、グローバルなコード初期化オブジェクトを使用しないもう1つの理由です。(グローバルコードで初期化されたオブジェクトがある場合に失敗するリンカースクリプトを使用します。)IMOのような「オブジェクト」は、明示的に作成され、渡される必要があります。これには、wait()関数を提供する「待機」機能「オブジェクト」が含まれます。私の設定では、これはチップのクロック速度を設定する「オブジェクト」です。

RAIIについて:これは、小規模な組み込みシステムで非常に便利なもう1つのC ++機能です。ただし、大規模システムで最もよく使用される理由(メモリの割り当て解除)ではありません(小規模な組み込みシステムは、動的メモリの割り当て解除をほとんど使用しません)。リソースをロックすることを考えてください。ロックされたリソースをラッパーオブジェクトにして、ロックラッパーを介してのみリソースへのアクセスを制限できます。ラッパーが範囲外になると、リソースのロックが解除されます。これにより、ロックせずにアクセスできなくなり、ロック解除を忘れにくくなります。いくつかの(テンプレート)マジックを使用すると、オーバーヘッドがゼロになります。


元の質問ではCに言及していなかったため、私のC ++中心の答えです。本当にCでなければならない場合...

マクロのトリックを使用することができます:stuctsをパブリックに宣言して、タイプを持ち、グローバルに割り当てることができますが、モジュールの.cファイルでマクロが異なるように定義されていない限り、コンポーネントの名前を使いやすさを超えてマングルします。セキュリティを強化するために、マングリングでコンパイル時間を使用できます。

または、有用なものが何もない構造体のパブリックバージョンを使用し、.cファイルのみに(有用なデータを含む)プライベートバージョンを作成し、それらが同じサイズであることを表明します。ちょっとしたメイクファイルの策略でこれを自動化できます。


@Lundinsは、悪い(組み込み)プログラマーについてコメントしています。

  • あなたが説明するプログラマーのタイプは、おそらくどの言語でも混乱するでしょう。マクロ(CおよびC ++に存在)は、1つの明らかな方法です。

  • ツーリングはある程度役立ちます。私の学生には、例外なし、rttiを指定し、ヒープが使用されるか、コードで初期化されたグローバルが存在する場合にリンカーエラーを発生させるビルドスクリプトを義務付けています。また、warning = errorを指定し、ほぼすべての警告を有効にします。

  • テンプレートの使用をお勧めしますが、constexprとコンセプトを使用すると、メタプログラミングの必要性はますます少なくなります。

  • 「混乱したArduinoプログラマー」Arduino(配線、ライブラリ内のコードの複製)プログラミングスタイルを、より簡単で安全で、より高速で小さなコードを生成できる最新のC ++アプローチに置き換えたいと思います。時間と力さえあれば…


この答えをありがとう!C ++の使用はオプションですが、私たちは会社でCを使用しています(明示的に言及していません)。私はCについて話しているImを人々に知らせるために質問を更新しました。
L.ハインリッヒ

なぜ(のみ)Cを使用していますか?多分これはあなたが何をしたいことはessentialy C.で実現(のごく一部)C ++です...少なくとも、C ++を検討するためにそれらを説得するチャンス与えます
はWouterバンOoijen

私の(最初の「実際の」組み込み趣味プロジェクト)で行うことは、コンストラクターで単純なデフォルトを初期化し、関連するクラスに別のInitメソッドを使用することです。もう1つの利点は、単体テストにスタブポインターを渡すことができることです。
ミシェルケイツァー

2
@Michel趣味のプロジェクトでは、言語を自由に選択できますか?C ++を取ります!
ウーターヴァンOoijen

4
それは組み込みのための良いC ++プログラムを書くために実際に完全に可能である一方、そして、問題がいくつか>すべての組み込みシステムのプログラマの50%が鳴く、混乱してPCのプログラマー、Arduinoの愛好家などなど、人々のこれらの種類は、単にそこにあるからということであることはできません使用しますあなたが彼らの顔にそれを説明しても、C ++のきれいなサブセット。C ++を渡すと、気付かないうちに、STL、テンプレートメタプログラミング、例外処理、多重継承などのすべてを使用します。そして、結果はもちろん完全なゴミです。残念ながら、これは10個の埋め込みC ++プロジェクトのうち8個が最終的にどのように処理されるかです。
ランディン

7

FreeRTOS(別のOSかもしれませんか?)は、2つの異なるバージョンの構造体を定義することで、探しているものと同じようなことができると思います。
OS関数によって内部的に使用される「実際の」ものと、「実際の」ものと同じサイズであるが、内部に有用なメンバーを持たない「偽の」もの(ほんの一群のint dummy1類似物)。
「偽の」構造体のみがOSコードの外部に公開され、これは構造体の静的インスタンスにメモリを割り当てるために使用されます。
内部的には、OSの関数が呼び出されると、外部の「偽」構造体のアドレスがハンドルとして渡され、これが「実際の」構造体へのポインタとして型キャストされるため、OS関数は必要な処理を実行できます行う。


良いアイデアだと思います--- #define BUILD_BUG_ON(condition)((void)sizeof(char [1-2 * !!(condition)]))--- BUILD_BUG_ON(sizeof(real_struct)!= sizeof( fake_struct))----
L.ハインリッヒ

2

匿名の構造体。したがって、構造体は、提供されたインターフェース関数の使用によってのみ変更できます。

私の意見では、これは無意味です。そこにコメントを入れることはできますが、それ以上隠そうとしても意味がありません。

Cは、そのような高い分離を決して提供しません。たとえ構造体の宣言がなくても、例えば誤ったmemcpy()またはバッファーオーバーフローで誤って上書きすることは容易です。

代わりに、構造体に名前を付けて、他の人にも良いコードを書くことを信頼してください。また、構造体に参照に使用できる名前がある場合、デバッグが容易になります。


2

純粋なSWに関する質問は、https://stackoverflow.comでより適切に尋ねられます

不完全な型の構造体を公開する概念あなたが説明するように、を呼び出し元には、しばしば「不透明型」または「不透明ポインタ」と呼ばれます-匿名構造体は完全に別のものを意味します。

これに関する問題は、呼び出し元がオブジェクトのインスタンスを割り当てることができず、オブジェクトへのポインタのみを割り当てることができることです。PCではmalloc、オブジェクト「コンストラクタ」の内部で使用しますが、mallocは組み込みシステムでは使用できません。

したがって、組み込みで行うことは、メモリプールを提供することです。RAMの容量は限られているため、作成できるオブジェクトの数を制限しても通常は問題ありません。

SO での不透明データ型の静的割り当てを参照してください。


私の名前の混乱を明確にしてくれてありがとう、OPを調整してください。スタックオーバーフローをしようと考えていましたが、特に組み込みプログラマをターゲットにしたいと思いました。
L.ハインリッヒ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.