私が考えることができる利点はありません(ただし、下部のJasonSのメモを参照)。1行のコードを関数またはサブルーチンとしてラップします。おそらく、関数に「読み取り可能な」名前を付けることができることを除いて。ただし、行にコメントを付けることもできます。また、関数内のコード行をラップすると、コードメモリ、スタック領域、および実行時間がかかるため、ほとんどの場合、非生産的。教育状況ですか?理にかなっているかもしれません。しかし、それは生徒のクラス、事前の準備、カリキュラム、教師によって異なります。たいてい、それは良い考えではないと思います。しかし、それは私の意見です。
それは私たちに最終的な結果をもたらします。あなたの幅広い質問領域は、何十年もの間、いくつかの議論の問題であり、今日までいくつかの議論の問題のままです。ですから、少なくともあなたの質問を読んだとき、私は意見に基づいた質問であるように思われます(あなたが尋ねたように)。
あなたが状況についてより詳細になり、あなたがプライマリとして保持した目的を慎重に説明することになった場合、それは意見に基づくものから離れることができます。測定ツールを適切に定義するほど、回答の客観性が高まります。
大まかに言えば、あなたがのために次の操作を行いたい任意のコーディング。(以下では、すべてが目標を達成するさまざまなアプローチを比較していると仮定します。明らかに、必要なタスクを実行できないコードは、作成方法に関係なく成功するコードよりも悪いです。)
- アプローチについて一貫性を保ち、コードを読んでいる別の人がコーディングプロセスへのアプローチ方法を理解できるようにします。一貫性のないことは、おそらく最悪の犯罪です。他の人にとってそれが難しくなるだけでなく、数年後にコードに戻るのが難しくなります。
- 可能な限り、さまざまな機能セクションの初期化を順序に関係なく実行できるように、物事を調整してください。順序付けが必要な場合、2つの関連性の高いサブ関数の密接な結合が原因である場合、両方の単一の初期化を検討して、害を及ぼすことなく並べ替えることができます。それが不可能な場合は、初期化順序の要件を文書化します。
- カプセル化された知識可能であれば、を1か所にます。定数は、コード内のすべての場所に複製しないでください。ある変数について解く方程式は、ただ1つの場所に存在する必要があります。等々。さまざまな場所で必要な動作を実行する行のセットをコピーして貼り付けることに気付いた場合は、その知識を1か所に取り込み、必要な場所で使用する方法を検討してください。たとえば、特定の方法で歩いてしなければならないツリー構造を持っている場合は、行いませんは、ツリーノードをループする必要があるすべての場所でツリーウォーキングコードを複製します。代わりに、ツリーウォークメソッドを1か所でキャプチャして使用します。このように、ツリーが変更され、ウォーキング方法が変更された場合、心配する場所は1つだけで、残りのコードはすべて「正しく機能します」。
- すべてのルーチンを他のルーチンから呼び出される矢印でつなげた巨大な平らな紙に広げると、多くの矢印を持つルーチンの「クラスター」がアプリケーションに表示されます。グループ間ではなく、少数の矢印のみ。したがって、密接に結合されたルーチンの自然な境界と、密接に結合されたルーチンの他のグループ間に疎結合された接続が存在します。この事実を使用して、コードをモジュールに編成します。これにより、コードの見た目の複雑さが大幅に制限されます。
上記は、一般的にすべてのコーディングについて当てはまります。パラメーター、ローカルまたは静的グローバル変数などの使用については説明しませんでした。理由は、組み込みプログラミングの場合、アプリケーション空間に極端で非常に重要な新しい制約が課せられることが多いためです。とにかく、それはここでは起きていません。
これらの制約は、次のいずれか(およびそれ以上)になります。
- 極小のRAMを備え、I / Oピン数がほとんどない非常に原始的なMCUを必要とする厳しいコスト制限。これらには、まったく新しいルールセットが適用されます。たとえば、コードスペースがあまりないため、アセンブリコードを記述する必要があります。ローカル変数の使用はコストと時間がかかりすぎるため、静的変数のみを使用する必要があります。(たとえば、一部のMicrochip PICパーツ)サブルーチン戻りアドレスを格納するハードウェアレジスタが4つしかないため、サブルーチンの過剰な使用を避ける必要がある場合があります。そのため、コードを劇的に「平坦化」する必要があります。等。
- MCUのほとんどを起動およびシャットダウンするために慎重に作成されたコードを必要とし、フルスピードで実行する場合のコードの実行時間に厳しい制限を課す厳しい電力制限。繰り返しますが、これにはいくつかのアセンブリコーディングが必要になる場合があります。
- 厳しいタイミング要件。たとえば、オープンドレイン0の送信が1の送信とまったく同じサイクル数をとる必要があることを確認しなければならない場合があります。また、この同じラインのサンプリングも実行する必要がありました。このタイミングに対する正確な相対位相で。つまり、ここではCを使用できません。この保証を行う唯一の方法は、アセンブリコードを慎重に作成することです。(それでも、すべてのALU設計で常にではありません。)
等々。(生命にかかわる医療機器の配線コードにも独自の世界があります。)
ここでの結果は、組み込みコーディングは、多くの場合、自由に使えるものではなく、ワークステーションでコーディングするのと同じようにコーディングできることです。多種多様な非常に困難な制約には、しばしば厳しい競争上の理由があります。そして、これらは強く、より反論も伝統と株式の答え。
読みやすさに関しては、読みながら学習できる一貫した方法で記述されている場合、コードは読みやすいことがわかります。そして、コードを難読化する意図的な試みがない場合。実際には、それほど多くは必要ありません。
読み取り可能なコードは非常に効率的であり、前述の要件をすべて満たすことができます。主なことは、コーディングするときに、アセンブリレベルまたはマシンレベルで記述する各コード行が何を生成するかを完全に理解することです。C ++ は、C ++コードの同一のスニペットが大幅に異なるパフォーマンスを持つマシンコードの異なるスニペットを実際に生成する多くの状況があるため、ここでプログラマに深刻な負担をかけます。しかし、一般的に、Cは大部分が「見たものが得たもの」言語です。その点でより安全です。
JasonSごとの編集:
私は1978年からC、1987年頃からC ++を使用しており、メインフレーム、ミニコンピューター、および(ほとんど)組み込みアプリケーションの両方で両方を使用した経験が豊富です。
ジェイソンは、「インライン」を修飾子として使用することについてコメントを出します。(私の視点では、これは比較的新しい機能です。なぜなら、CとC ++を使用すると、おそらく私の人生の半分以上は存在しなかったからです。)インライン関数を使用すると、コード)非常に実用的。また、可能な場合は、コンパイラが適用できる型付けのためにマクロを使用するよりもはるかに優れています。
しかし、制限もあります。1つ目は、コンパイラーに頼って「ヒントを得る」ことができないことです。それはそうかもしれないし、そうでないかもしれない。そして、ヒントを受け取らない正当な理由があります。(関数のアドレスが取得された場合に明白な例については、これは必要で関数のインスタンス化や...コールが必要になります電話をかけるには、アドレスの使用を。コードは、その後インライン化することはできません。)があります他の理由も同様です。コンパイラには、ヒントの処理方法を判断するためのさまざまな基準があります。そしてプログラマーとして、これはあなたがしなければならないことを意味しますコンパイラのその側面について学習するか、欠陥のあるアイデアに基づいて決定を下す可能性が高いです。そのため、コードの作成者だけでなく、読者だけでなく、コードを他のコンパイラに移植する予定の人にも負担がかかります。
また、CおよびC ++コンパイラは個別のコンパイルをサポートしています。これは、プロジェクトに関連する他のコードをコンパイルすることなく、CまたはC ++コードの一部をコンパイルできることを意味します。コードをインライン化するには、コンパイラーがそうすることを選択する可能性があると仮定すると、「スコープ内」の宣言が必要なだけでなく、定義も必要です。通常、プログラマは、「インライン」を使用している場合にこれが当てはまることを確認するように働きます。しかし、間違いが忍び込むのは簡単です。
一般的に、適切だと思う場所でもインラインを使用しますが、私はそれを信頼できないと思いがちです。パフォーマンスが重要な要件であり、OPが既に「機能的な」ルートに行ったときにパフォーマンスが大幅に低下したことを明確に書いていると思う場合、コーディングプラクティスとしてインラインに依存することを避け、代わりに、わずかに異なるが完全に一貫したコード記述パターンに従います。
「インライン」と、別のコンパイル手順の「スコープ内」にある定義に関する最後の注意。リンク段階で作業を実行することは可能です(常に信頼できるとは限りません)。これは、C / C ++コンパイラがオブジェクトファイルに十分な詳細を埋め込んで、リンカーが「インライン」リクエストに対応できるようにする場合にのみ発生します。私は個人的に、この機能をサポートするリンカーシステム(Microsoftの外部)を経験していません。しかし、それは起こる可能性があります。繰り返しますが、それを信頼すべきかどうかは状況に依存します。しかし、良い証拠に基づいて別の方法で知っている場合を除き、私は通常、これはリンカにシャベルされていないと仮定します。そして、私がそれに頼るなら、それは目立つ場所で文書化されるでしょう。
C ++
関心のある方のために、組み込みアプリケーションをすぐに入手できるにもかかわらず、組み込みアプリケーションをコーディングする際にC ++にかなり注意を払っている理由の例を次に示します。私はすべての組み込みC ++プログラマーが風邪を知る必要があると思ういくつかの用語を投げます:
- 部分テンプレート特化
- vtables
- 仮想ベースオブジェクト
- アクティベーションフレーム
- アクティベーションフレームの巻き戻し
- コンストラクターでのスマートポインターの使用とその理由
- 戻り値の最適化
これはほんの短いリストです。これらの用語についてのすべてをまだ知らない場合、およびそれらをリストした理由(そして、ここにリストしなかった他の多くの理由)は、プロジェクトのオプションでない限り、組み込み作業にC ++を使用することをお勧めします。
C ++例外セマンティクスを簡単に見て、フレーバーを取得してみましょう。
AB
A
.
.
foo ();
String s;
foo ();
.
.
A
B
C ++コンパイラは、foo()の最初の呼び出しを確認し、foo()が例外をスローした場合に、通常のアクティベーションフレームの巻き戻しを許可します。つまり、C ++コンパイラは、例外処理に関連するフレーム展開プロセスをサポートするために、この時点で余分なコードが必要ないことを知っています。
ただし、Stringが作成されると、C ++コンパイラは、後で例外が発生した場合、フレームの巻き戻しを許可する前に適切に破棄する必要があることを認識します。したがって、foo()の2番目の呼び出しは、最初の呼び出しと意味的に異なります。foo()の2回目の呼び出しが例外をスローする場合(例外を実行する場合もしない場合もあります)、コンパイラーは、通常のフレームの巻き戻しを行う前にStringの破壊を処理するように設計されたコードを配置する必要があります。これは、foo()の最初の呼び出しに必要なコードとは異なります。
(この問題を制限するためにC ++に追加の装飾を追加することは可能です。しかし、実際には、C ++を使用するプログラマーは、記述するコードの各行の意味をはるかに意識する必要があります。)
Cのmallocとは異なり、C ++の新機能は例外を使用して、未加工のメモリ割り当てを実行できないことを通知します。「dynamic_cast」も同様です。(C ++の標準的な例外については、Stroustrupの第3版、C ++プログラミング言語、384ページおよび385ページを参照してください。)コンパイラは、この動作を無効にすることができます。しかし、一般に、生成されたコード内の適切に形成された例外処理プロローグとエピローグにより、例外が実際に発生しない場合や、コンパイルされている関数に実際に例外処理ブロックがない場合でも、オーバーヘッドが発生します。(Stroustrupはこれを公に嘆きました。)
部分的なテンプレートの特殊化がない場合(すべてのC ++コンパイラがサポートしているわけではありません)、テンプレートを使用すると、組み込みプログラミングに大きな打撃を与える可能性があります。これがないと、コードブルームは深刻なリスクであり、フラッシュ内の小さなメモリの組み込みプロジェクトを殺す可能性があります。
C ++関数がオブジェクトを返すと、名前のないコンパイラ一時ファイルが作成され、破棄されます。一部のC ++コンパイラは、ローカルオブジェクトの代わりにオブジェクトコンストラクターがreturnステートメントで使用される場合に効率的なコードを提供できるため、1つのオブジェクトによる構築と破棄の必要性が減少します。しかし、すべてのコンパイラがこれを行うわけではなく、多くのC ++プログラマーは、この「戻り値の最適化」を認識していません。
オブジェクトコンストラクターに単一のパラメーター型を指定すると、C ++コンパイラーは、プログラマーにとってまったく予期しない方法で2つの型間の変換パスを見つけることができます。この種の「スマート」動作はCの一部ではありません。
スローされたオブジェクトは、オブジェクトの「動的タイプ」ではなく、catch句の「静的タイプ」を使用してコピーされるため、ベースタイプを指定するcatch句は、スローされた派生オブジェクトを「スライス」します。例外の悲惨さのまれではないソース(埋め込みコードに例外を追加する余裕さえあると感じるとき)
C ++コンパイラは、意図しない結果を伴うコンストラクタ、デストラクタ、コピーコンストラクタ、および代入演算子を自動的に生成できます。この詳細を使用して施設を取得するには時間がかかります。
派生オブジェクトの配列を基本オブジェクトの配列を受け入れる関数に渡すと、コンパイラー警告が生成されることはほとんどありませんが、ほとんどの場合、不正な動作が発生します。
C ++は、オブジェクトコンストラクターで例外が発生した場合、部分的に構築されたオブジェクトのデストラクターを呼び出さないため、コンストラクターで例外が発生した場合、コンストラクターで構築されたフラグメントが適切に破棄されることを保証するために、通常、「スマートポインター」が必要です。(Stroustrup、ページ367および368を参照)。これは、C ++で適切なクラスを記述する際の一般的な問題ですが、Cには構築および破棄のセマンティクスが組み込まれていないため、Cではもちろん回避されます。構築を処理する適切なコードの記述オブジェクト内のサブオブジェクトのこととは、C ++のこの独特のセマンティック問題に対処しなければならないコードを書くことを意味します。言い換えると、C ++のセマンティック動作を「書き回し」ます。
C ++は、オブジェクトパラメータに渡されたオブジェクトをコピーできます。たとえば、次のフラグメントでは、呼び出し「rA(x);」C ++コンパイラーはパラメーターpのコンストラクターを呼び出し、オブジェクトxをパラメーターpに転送するためにコピーコンストラクターを呼び出し、関数rAの戻りオブジェクト(名前のない一時的な)の別のコンストラクターを呼び出します。パラメータpからコピー。さらに悪いことに、クラスAが構築を必要とする独自のオブジェクトを持っている場合、これは悲惨に望遠鏡になります。(Cプログラマーはそのような便利な構文を持たず、一度にすべての詳細を表現する必要があるため、ACプログラマーはこのゴミのほとんどを回避し、手で最適化します。)
class A {...};
A rA (A p) { return p; }
// .....
{ A x; rA(x); }
最後に、Cプログラマー向けの短いメモ。longjmp()は、C ++では移植性のある動作をしません。(一部のCプログラマーは、これを一種の「例外」メカニズムとして使用します。)一部のC ++コンパイラーは、longjmpの取得時に実際にクリーンアップを試行しますが、その動作はC ++では移植できません。コンパイラが構築されたオブジェクトをクリーンアップすると、移植性がなくなります。コンパイラがそれらをクリーンアップしない場合、longjmpの結果としてコードが構築されたオブジェクトのスコープを離れ、動作が無効である場合、オブジェクトは破壊されません。(foo()でlongjmpを使用しても範囲が失われない場合、動作は問題ないかもしれません。)これは、C組み込みプログラマーによってあまり使用されることはありませんが、使用する前にこれらの問題を自覚する必要があります。