zz3star90
zz3star90

Reputation: 165

Safely printing all attributes of struct in C

I'm writing a toString method that prints out all attributes of an inputted pointer to a struct. In reading safe methods for handling strings, I finally created the below solution: note a Person struct has attributes name, weight, height, and age (all ints except for name, which is a char array).

char* toString(struct Person* inputPerson)
{
    // Allocate memory to return string
    char* ret = malloc(sizeof (char) * 100);
    // copy "Name: " into string
    strcpy(ret, "Name: ");
    // safely copy name, at most leaving enough room for the other params (leaving 50 bytes) 
    strncat(ret, inputPerson->name, 100-50);
    // copy "Age: " into string
    strcat(ret, "\n\tAge: ");
    // create tmp char to allow us to convert ints age, weight, and height into strings
    char tmp[4];
    // safely convert string to int (max number of digits being 3)
    snprintf(tmp, sizeof(tmp), "%d", inputPerson->age);
    // safely copy at most 3 characters, plus a null terminating char
    strcat(ret, tmp); // the previous snprintf makes sure that tmp is not too large.
    // repeat previous 2 steps for both weight and height attributes
    strcat(ret, "\n\tWeight: ");
    snprintf(tmp, sizeof(tmp),  "%d", inputPerson->weight);
    strcat(ret, tmp);
    strcat(ret, "\n\tHeight: ");
    snprintf(tmp, sizeof(tmp), "%d", inputPerson->height);
    strcat(ret, tmp);

    // Return a pointer to the string
    return ret;
}

My question is: is this overkill? All I want to do is print out each attribute safely and securely. For each string, I have to make sure it is a maximum size before appending. For each integer, I have to snprintf it into a string (ensuring a maximum allowed length) and then append that string to my return string. Is there a simpler way? Also my heebie jeebies are growing every time I look at the "100-50" code portion: how can I specify "size allocated to ret" instead of 100?

Upvotes: 2

Views: 1614

Answers (1)

zwol
zwol

Reputation: 140796

Since you are mallocing the return buffer anyway, why not make life easier on yourself by allocating a buffer of the correct size? As Joachim suggests, you can even do that with snprintf:

char* toString(struct Person* inputPerson)
{
    size_t space_required =
        snprintf(0, 0,
                 "Name: %s\n"
                 "\tAge: %d\n"
                 "\tWeight: %d\n"
                 "\tHeight: %d\n",
                 inputPerson->name,
                 inputPerson->age,
                 inputPerson->weight,
                 inputPerson->height);

    // space_required excludes the terminating NUL
    // sizeof(char) == 1 *by definition*
    char *ret = malloc(space_required+1);
    if (!ret)
        return 0;

    snprintf(ret, space_required+1,
             "Name: %s\n"
             "\tAge: %d\n"
             "\tWeight: %d\n"
             "\tHeight: %d\n",
             inputPerson->name,
             inputPerson->age,
             inputPerson->weight,
             inputPerson->height);
    return ret;
}

That probably seems awful repetitive. If your C library has asprintf, you can avoid the repetition:

char* toString(struct Person* inputPerson)
{
    char *ret;
    if (asprintf(&ret,
                 "Name: %s\n"
                 "\tAge: %d\n"
                 "\tWeight: %d\n"
                 "\tHeight: %d\n",
                 inputPerson->name,
                 inputPerson->age,
                 inputPerson->weight,
                 inputPerson->height) == -1)
        return 0;
    return ret;
}

If you don't have asprintf, you can implement it using malloc and vsnprintf. I'll leave that as an exercise ;-)

Upvotes: 6

Related Questions