コーディングの基礎
重要
これは Azure Sphere (レガシ) のドキュメントです。 Azure Sphere (レガシ) は 2027 年 9 月 27 日に 再提供されておりユーザーは現時点で Azure Sphere (統合) に移行する必要があります。 TOC の上にある Version セレクターを使用して、Azure Sphere (統合) のドキュメントを表示します。
アプリケーション コードは、このトピックで定義されている最低限の品質基準を満たしていることをお勧めします。 運用環境でデプロイされたアプリケーションの改善を目指すお客様とのパートナーシップを通じて、一般的な問題がいくつか見つかりました。この問題が修正されると、アプリケーションのパフォーマンスが向上します。
一般的な問題
- ターゲット API セットを設定する場合は、最新の CMake ツールと Azure Sphere ツールを使用し、最終的に
AZURE_SPHERE_TARGET_API_SET="latest-lts"
を設定して最終的なリリース バイナリをコンパイルすることをお勧めします。 詳細については、「 Coding for renewable security」を参照してください。
Note
製造プロセス内でサイドロードするイメージ パッケージを具体的に作成する場合は、デバイスのソースまたは回復先の適切な Azure Sphere OS バージョンに AZURE_SPHERE_TARGET_API_SET
を設定します。これを行わないと、Azure Sphere OS によってイメージ パッケージが拒否されます。
- アプリケーションを運用環境に デプロイする準備ができたら リリース モードで最終的なイメージ パッケージをコンパイルしてください。
- コンパイラの警告にもかかわらず、アプリケーションが運用環境にデプロイされるのが一般的です。 完全なビルドに対して警告ゼロ ポリシーを適用すると、すべてのコンパイラ警告が意図的に対処されます。 最も頻繁に発生する警告の種類を次に示します。これは、対処することを強くお勧めします。
- 暗黙的な変換関連の警告: バグは、最初の迅速な実装が原因で暗黙的な変換が行われるため、多くの場合、未通知のままでした。 たとえば、異なる数値型間で多くの暗黙的な数値変換を行うコードでは、精度の重大な損失や、計算エラーや分岐エラーが発生する可能性があります。 すべての数値型を適切に調整する場合は、キャストだけでなく、意図的な分析とキャストの両方をお勧めします。
- 予期されるパラメーター型の変更を避ける: API を呼び出すとき、明示的にキャストしない場合、暗黙的な変換によって問題が発生する可能性があります。たとえば、符号なし数値型ではなく符号付き数値型が使用されている場合にバッファーをオーバーランする場合などです。
- const-discarding warnings: 関数でパラメーターとして const 型が必要な場合、それをオーバーライドするとバグや予期しない動作が発生する可能性があります。 この警告の理由は、const パラメーターがそのまま残り、特定の API または関数を設計するときの制限を考慮するためです。
- 互換性のないポインターまたはパラメーターの警告: この警告を無視すると、後で追跡するのが難しいバグが隠されることがよくあります。 これらの警告を排除すると、他のアプリケーションの問題の診断時間を短縮できます。
- 一貫性のある CI/CD パイプラインを設定することは、古いアプリケーション リリースをデバッグするためのバイナリとその対応するシンボルを簡単に再構築できるため、持続可能な長期的なアプリケーション管理の鍵となります。 適切な分岐戦略はリリースの追跡にも不可欠であり、バイナリ データの格納にコストのかかるディスク領域を回避します。
メモリ関連の問題
- 可能な場合は、コードをより保守しやすいものにしながら、コードベース全体のデータ ポインターとして使用できるように、(
printf
コマンド内など) ハードコーディングするのではなく、すべての一般的な固定文字列をglobal const char*
として定義します。 実際のアプリケーションでは、ログや文字列操作 (OK
、Succeeded
、JSON プロパティ名など) から一般的なテキストを収集し、定数にグローバル化すると、読み取り専用データ メモリ セクション (.rodata とも呼ばれます) が節約され、他のセクションで使用できるフラッシュ メモリの節約に変換されることがよくあります (コードの追加に .text など)。 このシナリオは見過ごされることが多いですが、フラッシュ メモリを大幅に節約できます。
Note
上記は、コンパイラの最適化 (gcc での -fmerge-constants
など) をアクティブ化するだけでも実現できます。 この方法を選択した場合は、コンパイラの出力を調べて、必要な最適化が適用されていることを確認します。これは、コンパイラのバージョンによって異なる可能性があるためです。
- グローバル データ構造の場合は、可能な限り、動的に割り当てられたメモリにポインターを使用するのではなく、比較的小さい配列メンバーに固定長を指定することを検討してください。 次に例を示します。
typedef struct {
int chID;
...
char chName[SIZEOF_CHANNEL_NAME]; // This approach is preferable, and easier to use e.g. in a function stack.
char *chName; // Unless this points to a constant, tracking a memory buffer introduces more complexity, to be weighed with the cost/benefit, especially when using multiple instances of the structure.
...
} myConfig;
- 特に頻繁に呼び出される関数内では、可能な限り動的メモリ割り当てを避けてください。
- C では、メモリ バッファーへのポインターを返す関数を探し、参照されるバッファー ポインターとその関連するサイズを呼び出し元に返す関数に変換することを検討します。 これを行う理由は、バッファーへのポインターだけを返すと、多くの場合、呼び出し元のコードに問題が発生しています。これは、返されるバッファーのサイズが強制的に確認されないため、ヒープの整合性が危険にさらされる可能性があるためです。 次に例を示します。
// This approach is preferable:
MY_RESULT_TYPE getBuffer(void **ptr, size_t &size, [...other parameters..])
// This should be avoided, as it lacks tracking the size of the returned buffer and a dedicated result code:
void *getBuffer([...other parameters..])
動的コンテナーとバッファー
リストやベクターなどのコンテナーは、埋め込み C アプリケーションでも頻繁に使用されます。標準ライブラリを使用する場合のメモリ制限のため、通常は明示的にコード化するか、ライブラリとしてリンクする必要があります。 これらのライブラリの実装では、慎重に設計されていない場合に、メモリ使用量の集中をトリガーできます。
静的に割り当てられる一般的な配列やメモリが非常に動的な実装に加えて、増分割り当てアプローチをお勧めします。 たとえば、N 個の事前割り当て済みオブジェクトの空のキュー実装から始めます。(N+1) 番目のキュー プッシュでは、キューは固定 X 個の追加の事前割り当て済みオブジェクト (N=N+X) によって増加します。これは、キューへの別の追加機能が現在の容量をオーバーフローし、追加の事前割り当て済みオブジェクト X によってメモリ割り当てがインクリメントされるまで動的に割り当てられたままになります。 最終的には、未使用のメモリを再利用するために、(定期的に呼び出すには高価すぎるため) 慎重に呼び出す新しい圧縮関数を実装できます。
専用インデックスは、キューのアクティブ なオブジェクト数を動的に保持します。これは、オーバーフロー保護を強化するために最大値に制限できます。
この方法では、従来のキュー実装で連続メモリ割り当てと割り当て解除によって生成される "chatter" が排除されます。 詳細については、「 Memory の管理と使用方法を参照してください。 リスト、配列などの構造体にも同様のアプローチを実装できます。