node* new_node = malloc(sizeof(node));
printf("pointing to: %p\n", new_node->next);// points to head after pop() call
new_node->next
がmalloc
がそこに入れたいものは何でもゴミが含まれます。 がに頭を向けることがありますが、それは初期化していないので、printf
はゴミで意味を見つけようとしています。
あなたのコードはメモリ管理を全面的に分散します。それを修正しようとするよりも、構造体に関する私の株式アドバイスを使って書き直しましょう:常にそれらを初期化して破壊する関数を書きます。 常にです。たとえそれが愚かで些細なようであっても。これは、その場所のいたるところにコードを散らすことを避け、毎回わずかに異なるようにします。それはあなたがそれを使用しようとする前に構造体の基本的な機能をテストすることができます。これは、メモリ管理ではなく、アルゴリズムに焦点を当てることができます。
まず、構造体を調整しましょう。 node
は型にとって非常に悪い名前です。あなた(または他の誰か)が変数node
を呼び出して競合する可能性があります。私はそれをNode
と呼びました、変数と組み込み変数との混同を避けるために大文字にしました。
typedef struct Node {
int value;
struct Node* next;
struct Node* previous;
} Node;
今、私たちはNode_new
とNode_destroy
を書くことができます。
Node *Node_new() {
Node *node = malloc(sizeof(Node));
node->value = 0;
node->next = NULL;
node->previous = NULL;
return node;
}
void Node_destroy(Node *node) {
free(node);
}
Node_destroy
は愚かに見えるかもしれませんが、それは
Node
を破壊する方法を覚えておくことから、あなた(または他の誰か)を解放します。そして、それは
Node
の内部構造を(これを書いている間に起こった)残りのコードを変更することなく変更することができます。
グローバルを使用しています。グローバル化はすべてをより複雑にし、コードでできることを制限します。代わりに、head
、tail
、size
などのものを独自の構造にラップし、その周囲を渡します。
typedef struct {
Node *head;
Node *tail;
size_t size;
} LinkedList;
そして、独自の作成および破棄機能が必要です。 LinkedList_destroy
を心配し、潜在的に台無しにするために、すべてのノードで、LinkedListののユーザーのためにその1つの以下の事をクリーンアップするための責任を取ることを
LinkedList *LinkedList_new() {
LinkedList *list = malloc(sizeof(LinkedList));
list->head = NULL;
list->tail = NULL;
list->size = 0;
return list;
}
void LinkedList_destroy(LinkedList *list) {
for(Node *node = list->head; node != NULL; node = node->next) {
Node_destroy(list->head);
}
free(list);
}
注意。
LinkedList_destroy
Node
の仕組みについて知らずにNode_destroy
に電話することができます。これは、カプセル化と抽象化が即座にNode
の恩恵を受ける方法です。しかし、再帰を使用しないでください。リストは任意に長くなり、再帰はスタックのオーバーフローを引き起こします。
ここでは、物事が適切に作成され破棄されたことをプッシュアンドポップで書いています。それらは、グローバルを使用するのではなく、LinkedListをとることに注意してください。
void LinkedList_push(LinkedList *list, int value)
{
Node *node = Node_new();
node->value = value;
switch(list->size) {
/* The list is empty, this is the first node */
case 0:
list->head = list->tail = node;
break;
default:
list->tail->next = node;
node->previous = list->tail;
list->tail = node;
break;
}
list->size++;
}
int LinkedList_pop(LinkedList *list) {
Node *popped = list->tail;
switch(list->size) {
/* The list is empty, nothing to pop */
case 0:
fprintf(stderr, "LinkedList was empty when popped.\n");
exit(1);
break;
/* Popped the last node */
case 1:
list->head = list->tail = NULL;
break;
/* Only one node left, it's both the head and tail */
case 2:
list->tail = list->head;
list->tail->previous = list->tail->next = NULL;
break;
default:
list->tail = popped->previous;
list->tail->next = NULL;
break;
}
/* Have to do this at the end because size_t is unsigned
it can't go negative */
list->size--;
int value = popped->value;
Node_destroy(popped);
return value;
}
私はので、私はすべての特別な場合を明確に区別することができswitch
を使用しました。
これはプッシュアンドポップのベスト実装か、バグフリーでもないとは言いませんが、構造体が正しく初期化されたか解放されたかを気にせずに書き込むことができます。あなたは、メモリ管理ではなく、ロジックに集中することができます。
し、それを実証するためのすべての作品は...
void LinkedList_print(LinkedList *list) {
for(Node *node = list->head; node != NULL; node = node->next) {
printf("%d\n", node->value);
}
}
int main() {
LinkedList *list = LinkedList_new();
for(int i = 0; i < 3; i++) {
LinkedList_push(list, i);
}
while(list->size != 0) {
printf("list->size: %zu\n", list->size);
LinkedList_print(list);
LinkedList_pop(list);
}
LinkedList_destroy(list);
}
$ ./test
list->size: 3
0
1
2
list->size: 2
0
1
list->size: 1
0
これは未定義の動作と呼ばれています。変数が初期化されていない場合は、任意の(半)ランダム値を含むことができます。どのように起こったのか正確に理解しようとすることはありません。結果は予測不可能であり、コンパイラ、コンパイラオプション、周囲のコード、天気などによって変わる可能性があります。 – kaylum
[未定義の動作で実際に\ *何も起こらないのですか?](http: /stackoverflow.com/questions/32132574/does-undef-behavior-really-permit-anything-to-happen) – kaylum
@kaylum、確かに私はそれを理解していますが、明らかに、リストの先頭。実際にランダムな割り当てだった場合、その確率は非常に小さくなります。だから、それが具体的に頭を指しているのは正当な理由があるに違いない。私は理由が追跡可能であると確信している。 –