0

I am trying to get a better understanding of how one would structure an API in C.

  • I create a struct Person
  • I have a init function that sets data on that struct
  • I have multiple "helper" functions that work on that struct

I'm wondering if the following code in C can be considered idiomatic from the point of view of more seasoned C developers.

#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 particular I'm unsure about the signature of the functions in general and about the use of a Person * pointer.

When is it OK to not-pass a pointer but the struct/char array directly?

In most APIs I have seen so far, a pointer to the struct/char array is passed, like snprintf(char * s, ... ). But sometimes, like in getline(char **lineptr, ...) even a char ** is passed. Why? When make the distinction?

  • 1
    char * pretty much means "String" in old-style C, because there is no built-in String type. Read char ** as String * and things look much more consistent. – Kilian Foth Apr 7 '15 at 11:31
  • OK @KilianFoth, that makes sense. But when do you use one over the other? – Max Apr 7 '15 at 11:41
  • To expand a bit more, because to me it looks like getline could use a char * just as well? – Max Apr 7 '15 at 11:44
  • yah, it's nothing different from terminating a char string with '\0'. – robert bristow-johnson Apr 7 '15 at 16:21
2

Your question is a matter of opinion, however, it looks like Person is generally supposed to be allocated in the heap (read about C dynamic memory allocation).

Then you should define and document how Person-s are allocated and who is in charge of free-ing them and when.

I would suggest to have a function to create Person-s like

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;
}

We don't know what is next_person for. We might guess you want to have a global linked list of Person-s. Then you want some global pointer to the head of that list, the make_person should modify that global pointer, etc.

I would recommend against having Person automatic variables on the call stack (in particular, because that would limit the number of Person-s your program can handle, often to some staticly known small number; you may want to be able to handle millions of Person-s) Intuitively the call stack should not grow a lot (on current desktops with ordinary OSes like Linux or Windows, a stack limit of a few megabytes is typical) But we don't know what are you really coding.

Be afraid of undefined behavior, memory leaks. Read something about garbage collection. In a few months, you might be interested by Boehm's conservative garbage collector. If your system has it, consider using valgrind -for debugging purposes.

You might want a void destroy_person(Person*p) function which would either recursively (or iteratively) call destroy_person on the p->next_person or remove it from your global list, then call free(p) ...

  • Nothing in particular, I'm trying to understand how to structure an API. Why does it look to you like Person should be dynamically allocated? Those are the questions I am interested in. – Max Apr 7 '15 at 11:43
  • Ah, OK, I understand i guess … its better if my functions take a pointer, then the client code is free to either allocate on the heap or stack. Does that make sense? – Max Apr 7 '15 at 11:45
  • Practically not. You need to decide very early that every Person is heap allocated. – Basile Starynkevitch Apr 7 '15 at 11:47
2

Different linked libraries may use different heaps. This makes dynamic allocation and memory management tricky.

The most common way is to make Person an opaque struct (declared but not defined in the public header) and the programmer using it must use the functions in the library to get access to the attributes.

This means that allocating and freeing will be done inside the library using functions like Person* create_person(...) and void destroy_person(Person*).

  • Having different heaps for different libraries is uncommon, and definitely out of the scope of some beginner... Most libraries share the usual C heap (e.g. malloc & free) – Basile Starynkevitch Apr 7 '15 at 12:08
  • @BasileStarynkevitch but something to be aware of either way, easiest way to work around is to ensure only 1 library can allocate and destroy all instances of an object. – ratchet freak Apr 7 '15 at 12:10
  • But different heaps mean that libA is using the system malloc but libB is using some other malloc - e.g. its own one based on mmap - and this is not common. For most programmers, there is only one C heap, provided by system malloc & free functions. – Basile Starynkevitch Apr 7 '15 at 12:13
  • @BasileStarynkevitch or one of both uses a static C-runtime (with a maloc/free based on the same things as the system C-runtime but using its own globals) rather than one linked in by the system. Either way if you want an extended debate you can find me on programmer's chat. – ratchet freak Apr 7 '15 at 12:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.