Programming組み込み開発者

C言語における動的データ構造(例えば、連結リスト)の宣言と使用のニュアンスについて説明してください。実装時に特に注意すべき点は何ですか?

Hintsage AIアシスタントで面接を突破

答え。

C言語における動的データ構造(例えば、連結リストやツリー)は、通常、ポインタと動的メモリ確保(malloccallocfree)を使って手動で実装されます。

実装時の重要なニュアンス:

  • ポインタを必ず初期化する:初期化されていないポインタに不正な値があると、メモリリークやセグメンテーションフォールトが発生します。
  • メモリ確保のエラーを処理する:malloc/callocの結果をチェックしないと、プログラムが無効なポインタで動作する可能性があります。
  • メモリを正しく解放する:確保した各構造体に対してfreeを呼び出して、メモリリークが溜まらないようにします。
  • 空のポインタを避ける(freeの後にポインタをNULLに設定する)。

例:単純な単方向リストの作成と削除

typedef struct Node { int value; struct Node* next; } Node; Node* create_node(int value) { Node* n = malloc(sizeof(Node)); if (!n) return NULL; n->value = value; n->next = NULL; return n; } void free_list(Node* head) { while (head) { Node* tmp = head; head = head->next; free(tmp); } }

ひねりのある質問。

リストをループ内で、現在のポインタだけを使ってノードのメモリを解放できますか?

前もって次のノードを保存せずに現在のノードを解放することはできません!freeを呼び出した後、アドレスのメモリは上書きされるか、OSに返される可能性があります。

正しいアプローチ:

Node* curr = head; while (curr) { Node* next = curr->next; free(curr); curr = next; }

nextを保存しないと、既に解放された(そしておそらく他のプログラムの!)メモリにアクセスすることになります。


ストーリー


リスト全体を解放する(free)ことを忘れたために、操作の一つが失敗した際に自動テストが10000回の追加/削除操作を行い、徐々にメモリの消費が増加し、プロファイラが大きなメモリリークを示しました。


開発者はリストの最後のノードへのポインタを保持していましたが、すべての要素を削除した後にそれをNULLにしなかったため、別の関数で既に解放されたメモリを参照し、捕まえにくいsegfaultを引き起こしました。


ツリーの操作中にすべての「サブツリー」を再帰的に削除するのを忘れて、ルートノードだけを解放しました。結果:不完全なクリーンアップによりメモリ構造が汚染され、定期的にクラッシュが発生しました。