1

次のようなデータ構造、リンクリストがあります

struct listitem {
    void* data;
    struct listitem* next;
};
typedef struct listitem listitem;
typedef struct {
    listitem* head;
    pthread_rwlock_t lock;
} linkedlist;

データ構造をポリモーフィックにして、いくつかの異なるアプリケーションに使用できるようにするため、データに void* へのポインターを使用しています。

リストを初期化する (メモリを割り当て、rw ロックを初期化する) ために、それを関数 init_list(..) に渡します。次のようにリストへのポインターを渡すと、リストに対してさらに操作を実行しようとするたびにプログラムがハングします (たとえば、アイテムをリストにプッシュします)。

int init_list(linkedlist* list /* borrowed - list to initialise (probably unallocated) */) {
    list = (linkedlist*)calloc(1, sizeof(linkedlist));    // clear the memory, so that head is a null pointer
    printf("Allocated memory\n");
    if (list == 0) {
        perror("calloc failed on allocating memory for list");
        return 1;
    }
    printf("Initialising lock\n");
    pthread_rwlock_init(&list->lock, NULL);
    return 0;
}
...
linkedlist* ll;
init_list(ll);

上記はllが指すメモリをクリアし、ロックのメモリ位置を適切に初期化する必要があると私は理解しています。

ただし、リストへのポインターへのポインターを渡すと、すべて正常に動作します (つまり、ロックの取得やアイテムのリストへのプッシュなどの操作を実行しようとしても、プログラムはハングしません)。この余分な間接レイヤーを追加すると機能する理由がわかりません。どのように参照したかに関係なく、実際のメモリ位置に対する操作は同じだと思っていたでしょうか?

つまり、次の方法は機能しますが、最初のアプローチは機能しません。

int init_list(linkedlist** list /* borrowed - list to initialise (probably unallocated) */) {
    *list = (linkedlist*)calloc(1, sizeof(linkedlist));    // clear the memory, so that head is a null pointer
    printf("Allocated memory\n");
    if (list == 0) {
        perror("calloc failed on allocating memory for list");
        return 1;
    }
    printf("Initialising lock\n");
    pthread_rwlock_init(&(*list)->lock, NULL);
    return 0;
}
...
linkedlist* ll;
init_list(&ll);

最初のアプローチが機能しないのに、2 番目のアプローチが機能する理由を説明できません。

一般的なスタイルに関して、このアプローチは一般的ですか? または、C でデータ構造を初期化するためのより良い、より一般的な方法はありますか? 私は比較的新しい C プログラマーであり、コンストラクターでそのような初期化を行うことを期待するオブジェクト指向言語から来ており、C でそのスタイルをコピーしようとしています。必ずしも論理的ではない?

4

2 に答える 2

0

最初のケースでは、ポインタ (ll) のコピーをスタックにプッシュしました。あなたのコードはメモリを割り当てますsizeof(slinkedlist)。タイプミスだと思います.0で埋めてから、コピーを破棄します。呼び出し元が更新されたポインターを取得することはありません。llinit 後の値を出力します。2 番目の例では、ポインターをポインターにプッシュします。ポインターは割り当てられて初期化され、戻ったときに呼び出し元が喜んで使用できます。0 または 1 を返す代わりにll、戻り値を呼び出し元の変数に代入する場合は、最初のバージョンを使用できます。それは一種の機能的なスタイルになります。

于 2013-07-21T15:30:44.913 に答える
0

最初のバージョンでは、新しいメモリの割り当て、ゼロ化、およびパラメータへのコピーを行っています ( C はパラメータを値で送信するため、listこれは変わりません)。ll2 番目のバージョンには が*listあり、 list は へのポインターです。これは、変更すると実際に変更llされることを意味します(最初のバージョンとは異なります)。*listll

于 2013-07-21T15:18:37.603 に答える