Idiomatic C API per quanto riguarda i puntatori

0

Sto cercando di capire meglio come si strutturerebbe un'API in C.

  • creo una struct Person
  • Ho una funzione init che imposta i dati su quella struttura
  • Ho più funzioni "helper" che funzionano su quella struttura

Mi chiedo se il seguente codice in C possa essere considerato idiomatico dal punto di vista di sviluppatori C più esperti.

#include <stdio.h>
#include <stdlib.h>

typedef struct Person Person;

struct Person
{
    unsigned int age;
    unsigned int weight;
    Person *next_person;
};

void person_init(Person *p, unsigned int age, unsigned int weight, Person *next_person) 
{
    p->age = age;
    p->weight = weight;
    p->next_person = next_person;
}

void person_print(Person *p)
{
    printf("person is %dy old\n", p->age);
    printf("person weight is %dkg\n", p->weight);
}

int main(void) 
{
    Person p1, p2, p3, p4;
    Person *p_cur;

    person_init(&p1, 28, 80, &p2);    
    person_print(&p1);

    person_init(&p2, 58, 93, &p3);  
    person_print(&p2);

    person_init(&p3, 60, 60, &p4);  
    person_print(&p3);

    person_init(&p4, 78, 50, NULL);  
    person_print(&p4);

    printf("==================\n");

    p_cur = &p1;

    while (p_cur != NULL) {
        person_print(p_cur);
        p_cur = p_cur->next_person;
    }

    return 0;
}

In particolare, non sono sicuro della firma delle funzioni in generale e dell'uso di un puntatore Person * .

Quando va bene non passare un puntatore ma direttamente la struct / char array?

Nella maggior parte delle API che ho visto finora, viene passato un puntatore all'array struct / char, come snprintf(char * s, ... ) . Ma a volte, come in getline(char **lineptr, ...) , viene passato anche un char ** . Perché? Quando fai la distinzione?

    
posta Max 07.04.2015 - 13:30
fonte

2 risposte

2

La tua domanda è una questione di opinione , tuttavia sembra che Person sia in genere allocata nell'heap (leggi about allocazione della memoria dinamica C ).

Quindi dovresti definire e documentare come vengono allocati Person -s e chi è responsabile di free -in loro e quando.

Suggerirei di avere una funzione per creare Person -s come

Person* make_person(unsigned p_age, p_weight) {
   Person* p = malloc(sizeof(Person));
   if (!p) { perror("malloc"); exit(EXIT_FAILURE); };
   p->age = p_age;
   p->weight = p_weight;
   p->next_person = NULL;
}

Non sappiamo per cosa è next_person per. Potremmo immaginare che tu voglia avere un elenco globale di Person -s. Poi vuoi un puntatore globale all'inizio dell'elenco, il make_person dovrebbe modificare quel puntatore globale, ecc.

Suggerirei di non avere Person variabili automatiche nello stack di chiamate (in particolare, perché ciò limiterebbe il il numero di Person -s che il tuo programma può gestire, spesso con un numero piccolo statisticamente noto, potresti voler essere in grado di gestire milioni di Person -s) Intuitivamente lo stack delle chiamate non dovrebbe crescere molto (sui desktop correnti con sistemi operativi ordinari come Linux o Windows, un limite di stack di pochi megabyte è tipico) Ma non sappiamo cosa stai veramente codificando.

Temere comportamento non definito , perdite di memoria . Leggi qualcosa sulla garbage collection . Tra qualche mese potresti essere interessato a il garbage collector conservatore di Boehm . Se il tuo sistema lo possiede, considera l'utilizzo di valgrind per scopi di debug.

Potresti desiderare una funzione void destroy_person(Person*p) che ricorsivamente (o in modo iterativo) chiama destroy_person su p->next_person o rimuova dal tuo elenco globale, quindi chiama free(p) ...

    
risposta data 07.04.2015 - 13:36
fonte
2

Diverse librerie collegate possono utilizzare diversi heap. Ciò rende difficile l'allocazione dinamica e la gestione della memoria.

Il modo più comune è di rendere Person una struttura opaca (dichiarata ma non definita nell'intestazione pubblica) e il programmatore che la utilizza deve utilizzare le funzioni nella libreria per ottenere l'accesso agli attributi.

Ciò significa che l'allocazione e la liberazione verranno eseguite all'interno della libreria utilizzando funzioni come Person* create_person(...) e void destroy_person(Person*) .

    
risposta data 07.04.2015 - 14:06
fonte

Leggi altre domande sui tag