Question

I have a small program that creates a semver struct with some variables in it:

typedef struct {
    unsigned major;
    unsigned minor;
    unsigned patch;
    char * note;
    char * tag;
} semver;

Then, I would like to create a function which creates a semver struct and returns it to the caller. Basically, a Factory.

That factory would call an initialize function to set the default values of the semver struct:

void init_semver(semver * s) {
    s->major = 0;
    s->minor = 0;
    s->patch = 0;

    s->note = "alpha";

    generate_semver(s->tag, s);
}

And on top of that, I would like a function to generate a string of the complete semver tag.

void generate_semver(char * tag, semver * s) {
    sprintf( tag, "v%d.%d.%d-%s",
        s->major, s->minor, s->patch, s->note);
}

My problem appears to lie in this function. I have tried returning a string, but have heard that mallocing some space is bad unless you explicitly free it later ;) In order to avoid this problem, I decided to try to pass a string to the function to have it be changed within the function with no return value. I'm trying to loosely follow something like DI practices, even though I'd really like to separate the concerns of these functions and have the generate_semver function return a string that I can use like so:

char * generate_semver(semver * s) {
    char * full_semver;
    sprintf( full_semver, "v%d.%d.%d-%s",
        s->major, s->minor, s->patch, s->note);
    return full_semver; // I know this won't work because it is defined in the local stack and not outside.
}

semver->tag = generate_semver(semver);

How can I do this?

Was it helpful?

Solution

My problem appears to lie in this function. I have tried returning a string, but have heard that mallocing some space is bad unless you explicitly free it later.

Explicitly freeing dynamically allocated memory is required to avoid memory leaks. However, it is not necessarily a task that the end users need to perform directly: an API often provides a function to deal with this.

In your case, you should provide a deinit_semver function that does the clean up of memory that init_semver has allocated dynamically. These two functions behave in a way that is similar to constructor and destructor; init_semver is not a factory function, because it expects the semver struct to be allocated, rather than allocating it internally.

Here is one way of doing it:

void init_semver(semver * s, int major, int minor, int pathc, const char * note) {
    s->major = major;
    s->minor = minor;
    s->patch = pathc;

    size_t len = strlen(note);
    s->note = malloc(len+1);
    strcpy(s->note, note);
    s->tag = malloc(40 + len);
    sprintf(s->tag, "v%d.%d.%d-%s", major, minor, patch, note);
}
void deinit_semver(semver *s) {
    free(s->note);
    free(s->tag);
}

Note the changes above: rather than using fixed values for the components of struct semver, this code takes the values as parameters. In addition, the code copies the note into a dynamically allocated buffer, rather than pointing to it directly.

The deinit function does the clean-up by free-ing both fields that were allocated dynamically.

OTHER TIPS

A char * on its own is just a pointer to memory. To accomplish what you want you will either need to instead use a fixed size field, i.e. char[33], or you can dynamically allocate the memory as needed.

As it is, your generate_semver function is attempting to print to an unknown address. Let's look at one solution.

typedef struct {
    unsigned major;
    unsigned minor;
    unsigned patch;
    char     note[32];
    char     tag[32];
} semver;

Now, in your init_semver function, the line previously s->note = "alpha"; will become a string copy, as arrays are not a valid lvalue.

strncpy(s->note, "alpha", 31);
s->note[31] = '\0';

strncpy will copy a string from the second parameter to the first up to the number of bytes in the third parameter. The second line ensures that a trailing null terminator is in place.

Similarly, in the generate_semver function, it would directly work in the buffer:

void generate_semver(semver * s) {
    snprintf( s->tag, 32, "v%d.%d.%d-%s",
        s->major, s->minor, s->patch, s->note);
}

This will directly print to the array in the structure, with a maximum character limit. snprintf does append a trailing null terminator (unlike strncpy), so we don't need to worry about adding it ourselves.

You mention having to free allocated memory, and then say: "In order to avoid this problem". Well, it's not so much a problem, but rather a necessity of the C language. It's common to have functions that allocate memory, and require the caller to free it again.

The idiomatic way is to have a pair of "create" and "destroy" functions. So I'd suggest doing it like this:

// Your factory function
semver* create_semver() {
    semver* instance = malloc(sizeof(*instance));
    init_semver(instance); // will also allocate instance->tag and ->note
    return instance;
}

// Your destruction function
void free_semver(semver* s) {
    free(semver->tag);
    free(semver->note);
    free(semver);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top