user12340167
user12340167

Reputation:

snprintf with unknown size of the char[]

How one can create a char array with an unspecified size and then pass it to snprint for formatting it?
For example:

char str[];
int total = 100;
int points = 20;
snprintf(str, sizeof(str), "You have %d points (total: %d)", points, total);
printf("%s\n",str);

This code is going certainly send me an error because first line is wrong.
All the examples I saw on internet had something like str[100].
I am new to C programming and I would appreciate it if you don't down vote this post.
Thank you

Upvotes: 0

Views: 1006

Answers (2)

Koldar
Koldar

Reputation: 1407

C doesn't work like your typical high-level language. You are required to alloc every bit of memory you want to use (either be it on the stack or on the heap). That's why on the internet people usually do stuff like char str[100]: they are saying: I'm not expecting a string longer than 99 characters (+ '\0' char at the end of the string).

If you want your typical Java string, you need to create it yourself from scratch. Here's a demo of an implementation with no-so-good performances. variadic string api is left to the reader :)

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

struct string_t {
    ///number of bytes of the string (\0 included)
    size_t capacity;
    ///strlen of the string
    size_t len;
    ///pointer to the actual string
    char* str;
};

struct string_t* newString(const char* str);
void initString(struct string_t* this);
void destroyString(const struct string_t* this);
bool isEmpty(const struct string_t* this);
void tryExpand(struct string_t* this, size_t additionalSpace);
char* getNullTerminatorAddress(const struct string_t* this);
void setString(struct string_t* this, const char* other);

struct string_t* newString(const char* str) {
    struct string_t* result = malloc(sizeof(struct string_t));
    if (result == NULL) {
        fprintf(stderr, "error!\n");
        exit(1);
    }
    initString(result);
    setString(result, str);
    return result;
}

void initString(struct string_t* this) {
    this->len = 0;
    this->capacity;
    this->str = 0;
}

void destroyString(const struct string_t* this) {
    if (this->str != NULL) {
        free((char*)this->str);
    }
    free((char*)this);
}

bool isEmpty(const struct string_t* this) {
    return this->len == 0;
}

void tryExpand(struct string_t* this, size_t additionalSpace) {
    if ((this->len + additionalSpace) > this->capacity) {
        if (this->str != NULL) {
            int newcapacity = this->len + additionalSpace;
            this->str = realloc(this->str, newcapacity);
            this->capacity = newcapacity;
        } else {
            this->str = malloc(sizeof(char) * additionalSpace);
            if (this->str == NULL) {
                exit(1);
            }
        }
    }
}

void trySetting(struct string_t* this, size_t spaceRequired) {
    if ((spaceRequired) > this->capacity) {
        if (this->str != NULL) {
            int newcapacity = spaceRequired;
            this->str = realloc(this->str, newcapacity);
            this->capacity = newcapacity;
        } else {
            this->str = malloc(sizeof(char) * spaceRequired);
            if (this->str == NULL) {
                exit(1);
            }
            this->capacity = spaceRequired;
        }
    }
}

char* getNullTerminatorAddress(const struct string_t* this) {
    if (this->str != NULL) {
        return &this->str[this->len];
    } else {
        exit(2);
    }
}

void setString(struct string_t* this, const char* other) {
    int alen = strlen(other);
    trySetting(this, alen + 1);
    //beware of buffer overrun
    strcpy(this->str, other);
    this->len = alen;
}

void appendString(struct string_t* this, const char* other) {
    int alen = strlen(other);
    tryExpand(this, alen + 1);
    //beware of buffer overrun
    strcpy(getNullTerminatorAddress(this), other);
    this->len += alen;
}

void appendInt(struct string_t* this, int other) {
    int bytes = snprintf( NULL, 0, "%d", other); //0 not considered
    tryExpand(this, bytes + 1);
    snprintf(getNullTerminatorAddress(this), bytes+1, "%d", other);
    this->len += bytes;
}

const char* getString(const struct string_t* this) {
    return (const char*)this->str;
}

int getLength(const struct string_t* this) {
    return this->len;
}

int getCapacity(const struct string_t* this) {
    return this->capacity;
}

int main() {
    int points = 5;
    int total = 10;
    struct string_t* str = newString("You have ");
    appendInt(str, points);
    appendString(str, " points (total: ");
    appendInt(str, total);
    appendString(str, ")");

    printf("%s\n", getString(str));
    destroyString(str);
    return 0;
}

Note you can retrieve the number of bytes to write in snprintf thanks to the trick of user2622016

Upvotes: 0

foreverska
foreverska

Reputation: 585

At some point you must know how long your string will be. Simple examples like this you can just choose a sufficiently big number. You're doing the right thing using snprintf if you happen to be wrong at some point. In cases where this isn't possible snprintf returns the number of characters it will use if the first two parameters are NULL and 0. From there you malloc and free buffers determined at runtime.

size_t size = snprintf(NULL, 0, ...);
char* str = malloc(size);
snprintf(str, size, ...);
//After doing what you want with the string
free(str);

Upvotes: 2

Related Questions