Terapkan daftar tertaut umum yang memiliki bidang nilai tunggal

Baru-baru ini, ketika menulis beberapa program linux di c, sepertinya banyak tempat yang memerlukan daftar tertaut umum yang dapat mendukung berbagai jenis nilai, jadi saya mencoba menerapkannya, tetapi masih ada beberapa pertanyaan.

Pendekatan:

  • tentukan sebuah struct dengan pointer, lalu akhiri dengan bidang nilai bertipe char[], gunakan itu sebagai struct umum,
  • tentukan & impl metode pada daftar tertaut, menggunakan struct umum,
  • tentukan tipe struct baru yang memiliki bidang nilai tipe berbeda,
  • saat memanggil fungsinya, masukkan saja sesuatu sebagai tipe umum,

Kode: (versi draf)

linked_list.h

#ifndef _LINKED_LIST
#define _LINKED_LIST

// common list for any type,
struct llist_item {
    struct llist_item *next;
    char value[1];
};

// int list
struct llist_int {
    struct llist_int *next;
    int value;
};

/**
 * append item to end of list,
 * 
 * @param headp
 *  pointer to head pointer,
 * @param valuep
 *  pointer set value of deleted item into,
 * @param value_size
 *  size of value,
 * @param struct_size
 *  size of actual struct,
 * 
 * @return
 *  pointer to head,
 */
extern struct llist_item *llist_append(struct llist_item **headp, void *valuep, ssize_t value_size, ssize_t struct_size);

/**
 * delete head,
 * 
 * @param headp
 *  pointer to head pointer,
 * @param valuep
 *  pointer set value of deleted item into,
 * 
 * @return
 *  pointer to new head,
 */
extern struct llist_item *llist_del_head(struct llist_item **headp, char *valuep);

#endif

linked_list.c

// linked_list utility
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <stdlib.h>

#include "linked_list.h"

/*
   printf("error while linked_list: %s\n", strerror(errno));
   printf("linked_list succeed\n");
 */

struct llist_item *llist_append(struct llist_item **headp, void *valuep, ssize_t value_size, ssize_t struct_size) {
    struct llist_item *head = *headp;

    // create new item
    struct llist_item *new_item = (struct llist_item*) malloc(struct_size);
    new_item->next = NULL;
    memcpy(&(new_item->value), valuep, value_size);

    // append new item
    if(head == NULL) { // empty queue,
        head = new_item;
        *headp = head;
    } else {
        // find last item
        struct llist_item *tail = head;
        while(tail->next != NULL) {
            tail = tail->next;
        }   

        tail->next = new_item;
    }

    return head;
}

struct llist_item *llist_del_head(struct llist_item **headp, char *valuep) {
    struct llist_item *head = *headp;

    if(head == NULL) {
        return NULL;
    } else {
        memcpy(valuep, &(head->value), sizeof(*valuep));
        *headp = head->next;
        free(head);
        return *headp;
    }
}

llist_test.c

// linked_list test
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <stdlib.h>

#include "linked_list.h"

int linked_list_test() {
    struct llist_int *int_list = NULL; // it's important to initialize this pointer as NULL explicitly,
    int i;

    for(i=1; i<=5; i++) {
        llist_append((struct llist_item **) &int_list, (void *) &i, sizeof(int), sizeof(struct llist_int));
    }

    struct llist_int *int_item;
    int value;
    if(int_list != NULL) {
        do {
            (struct llist_int *)llist_del_head((struct llist_item **) &int_list, (char *) &value);
            printf("%d\n", value);
        } while (int_list!= NULL);
    }

    return 0;
}

int main(int argc, char * argv[]) {
    return linked_list_test();
}

Kompilasi & Jalankan

daftar kode:

  • linked_list.h, tajuk,
  • linked_list.c, implementasi,
  • llist_test.c, lakukan tes,

kompilasi - untuk pengujian:

gcc -Dinding tertaut_list.c llist_test.c -o a.out

eksekusi:

./a.keluar


Pertanyaan:

  • Pengecorannya rumit, apakah ada pendekatan untuk menyederhanakannya?
  • Dalam metode pengujian linked_list_test():

    jika berubah:

        do {
            int_item = (struct llist_int *)llist_del_head((struct llist_item **) &int_list, (char *) &value);
            printf("%d\n", value);
        } while (int_item != NULL);
    

    to

        do {
            (struct llist_int *)llist_del_head((struct llist_item **) &int_list, (char *) &value);
            printf("%d\n", value);
        } while (int_list!= NULL);
    

    Maka hasilnya adalah penggunaan, bukan keluaran:

    1 2 3 4 5

    itu keluaran:

    32513 32514 32515 32516 32517

    Yang membedakan adalah cast pointernya, kenapa hasilnya berbeda?


@Pembaruan - Tentang pertanyaan kedua

Seperti yang dijelaskan @BLUEPIXY dalam komentar, memang sizeof(*valuep) yang menyebabkan masalah, sekarang saya memodifikasi llist_del_head(), dan memberikan ukuran dalam daftar param secara eksplisit, dan masalah diperbaiki.

Fungsinya sekarang terlihat seperti ini:

extern struct llist_item *llist_del_head(struct llist_item **headp, char *valuep, ssize_t value_size);

person user218867    schedule 23.01.2016    source sumber
comment
Sudahkah Anda mencoba menelusuri kode, baris demi baris, di debugger?   -  person Some programmer dude    schedule 23.01.2016
comment
catatan sizeof(*valuep) adalah 1   -  person BLUEPIXY    schedule 23.01.2016
comment
@JoachimPileborg Kodenya bagus sekarang, sebelum itu saya melakukan beberapa debug melalui GDB untuk membuatnya berfungsi. Sekarang mohon saran tentang desain & implementasi, dan pertanyaan ke-2 hanyalah masalah penggunaan pointer yang tidak dapat saya pahami di gdb.   -  person user218867    schedule 23.01.2016
comment
@BLUEPIXY Itu penting, saya hampir lupa, akan periksa sekarang.   -  person user218867    schedule 23.01.2016
comment
Selain itu, alasan mengapa banyak cast diperlukan adalah karena Anda menggunakan alamat objek (misalnya &something). Saat Anda mengambil alamat suatu objek dengan operator & urnary, hasilnya hanyalah sebuah alamat. Sebuah alamat tidak memiliki tipe -- ini hanya sebuah lokasi memori. Oleh karena itu, untuk memanfaatkan alamat objek dengan benar, Anda harus mentransmisikan alamat objek ke ketik yang tepat.   -  person David C. Rankin    schedule 23.01.2016
comment
@BLUEPIXY hei, dengan tip Anda, memang sizeof(*valuep) yang menyebabkan masalah, sekarang saya memodifikasi llist_del_head() untuk memberikan ukuran dalam daftar param secara eksplisit, dan masalah telah diperbaiki, terima kasih.   -  person user218867    schedule 23.01.2016
comment
@David Ketika alamat operator (&) diterapkan ke objek apa pun yang valid, hasilnya selalu berupa nilai yang diketik. Jika diterapkan pada int, tipenya adalah int *. Jika diterapkan pada char [5], hasilnya adalah char (*)[5]. Setiap ekspresi dalam C memiliki tipe, baik pointer atau lainnya.   -  person Tom Karzes    schedule 23.01.2016
comment
Tom, saya setuju dengan itu, poin yang ingin saya sampaikan, mungkin secara tidak sengaja, adalah bahwa setelah Anda mengambil alamatnya, hasilnya adalah alamat kosong yang harus Anda gunakan agar dapat digunakan dengan benar (jika tidak menugaskan/melewati tipe yang sama). Ya, kompiler akan memunculkan peringatan/kesalahan tipe jika Anda mencoba penugasan tanpa pemeran yang tepat ke objek dengan tipe berbeda.   -  person David C. Rankin    schedule 23.01.2016
comment
@David Seharusnya jarang membutuhkan pemeran seperti itu. Biasanya jika Anda mengambil alamat sesuatu, dan meneruskannya ke suatu fungsi atau menugaskannya ke sebuah pointer, jenisnya cocok dan tidak diperlukan cast (yang merupakan inti dari pengetikan yang ketat). Satu-satunya saat casting diperlukan adalah untuk situasi yang tidak biasa di mana Anda perlu melakukan aritmatika alamat kosong (menghindari sistem tipe), atau jika Anda ingin memaksakan sesuatu seperti kualifikasi const dalam situasi yang tidak disukai kompiler.   -  person Tom Karzes    schedule 23.01.2016


Jawaban (2)


Pendekatan ini mempunyai beberapa masalah:

  • Ruang yang Anda sisihkan dalam struktur hanya 1 byte, Anda tidak dapat menyimpan tipe yang lebih besar dari 1 byte ke dalamnya.
  • Jika Anda membuat ruang untuk tipe yang lebih besar di sana, Anda masih akan mengalami masalah penyelarasan: tipe yang lebih besar mungkin memerlukan penyelarasan yang berbeda dari yang diasumsikan oleh kompiler untuk larik char setelah struct llist_item *. Jadi, bahkan menggunakan array tambahan berukuran variabel dalam struktur tidak memberikan solusi yang layak.

Anda dapat menggunakan void * dan mengalokasikan nilainya secara terpisah, namun hal ini sia-sia, atau menggunakan makro untuk mengimplementasikan templat kode sumber untuk tipe berbeda, namun hal ini rumit dan rawan kesalahan.

C tidak memiliki konstruksi yang tepat untuk konsep semacam ini, C++ memiliki kompleksitas yang jauh lebih besar.

person chqrlie    schedule 23.01.2016
comment
Tip bagus untuk bagian penyelarasan. - person user218867; 23.01.2016

Saya tidak yakin apa yang ingin Anda lakukan, tapi saya menyarankan:

  1. Jangan ekspor definisi daftar di file .h. Pengguna tidak perlu mengetahui implementasinya.

  2. Perhatikan bahwa struct llist_item Anda hanya dapat menampung satu karakter. Ubah menjadi void * sehingga dapat menyimpan penunjuk ke jenis nilai apa pun.

  3. harus meneruskan ukuran struct di llist_append tampaknya sangat aneh karena ini adalah ukuran struktur daftar ditambah ukuran nilai yang akan disimpan. Tapi ini ada hubungannya dengan bagaimana Anda ingin menyimpan "nilai apa pun"

Secara umum, pengguna ingin Anda mempertahankan penunjuk ke data yang harus disimpan dalam daftar. File .h Anda mungkin terlihat seperti:

/* append pointer valuep to the list */
void *llist_append(void **list, void *valuep, ssize_t value_size);

/* delete the list element that contains pointer valuep */
void *llist_del_head(void **list, void *valuep);

dan implementasi Anda:

// common list for any type,
struct llist_item {
    struct llist_item *next;
    void *value;
};

dan untuk menambahkan:

// create new item
struct llist_item *new_item = malloc(sizeof(struct llist_item));
new_item->next = NULL;
new_item->value= valuep;
person Paul Ogilvie    schedule 23.01.2016
comment
0)Alasan penulisan kode ini adalah untuk menggunakan kembali kode untuk jenis nilai yang berbeda. 1) Tip yang bagus, mungkin akan menghapusnya jika akan memberikannya kepada orang lain. 2) Saya memang mempertimbangkan tipe void * untuk bidang value, tetapi tipe dasar tidak akan pernah benar-benar digunakan, ketika membutuhkan tipe baru maka tentukan struct baru, saya menggunakan char[1] terutama karena saya melihat bidang unsigned char __pad[X]; dari struct sockaddr_in saat menulis program soket. 3) Itulah satu-satunya solusi yang saya tahu untuk memastikan ukurannya benar, saya melihat pendekatan serupa di inet_ntop(). - person user218867; 23.01.2016
comment
Saya baru saja mencoba void * value, sepertinya perlu mengalokasikan memori keluar dari situs struct itu sendiri, yang rumit untuk dipelihara. Jika tidak, akan terjadi kesalahan segmen. - person user218867; 23.01.2016
comment
Pemeliharaannya tidak lebih rumit daripada solusi Anda sebelumnya. Saat menghapus sebuah node, hapus saja (bebaskan) nilainya. Alternatifnya, daripada melakukan mallocing/menyalin nilai, Anda dapat menyimpan nilai (pointer) dari pengguna sehingga Anda tidak perlu mengalokasikan memori untuk itu. Ini tergantung pada fungsionalitas yang ingin Anda tawarkan kepada pengguna Anda: apakah dia ingin Anda menyimpan salinannya atau hanya sebuah penunjuk? Dalam kasus pertama, perubahan pada salinan pengguna tidak akan tercermin dalam salinan yang disimpan dalam daftar, dalam kasus kedua pengguna beroperasi pada satu salinan. - person Paul Ogilvie; 23.01.2016
comment
Oke, sepertinya di c sangat sulit untuk menulis solusi umum untuk ini, penggunaan yang berbeda lebih memilih solusi & struktur yang berbeda. - person user218867; 23.01.2016
comment
Eric, ini tidak sulit di C. Secara umum, pengguna ingin Anda mempertahankan pointer dan bukan salinannya. Pengguna meneruskan pointer ke sesuatu, yang diperlakukan oleh modul daftar Anda sebagai void *. Apa itu sesuatu, terserah pengguna dan modul daftar Anda tidak perlu mengetahui apa pun tentang apa yang disimpannya. Itu hanya menyimpan daftar. Saya akan memperbarui solusi untuk ini. - person Paul Ogilvie; 23.01.2016
comment
Jika itu hanya penunjuk, itu mudah. Namun dalam beberapa kasus lebih mudah bagi pengguna jika tidak perlu mengalokasikan memori tambahan oleh pengguna, misalnya pengguna ingin mempertahankan daftar soket fd dari klien, fd bertipe int, pengguna mungkin tidak ingin mengalokasikan ruang untuk int dan bebaskan nanti dengan tangan setiap kali saya kira. - person user218867; 23.01.2016
comment
Kemudian pengguna meneruskan int dan daftar menyimpan int di ruang untuk void * karena secara umum sizeof(int)==sizeof(void *) (catatan: standar tidak mengharuskan ukurannya sama tetapi di hampir semua platform ukurannya sama. Jadi secara formal Anda harus memeriksanya berukuran sama di platform Anda). - person Paul Ogilvie; 24.01.2016