3

I want to concatenate in a string multiple sentences. At the moment my buffer is with fixed size 100, but I do not know the total count of the sentences to concatenate and this size can be not enough in the future. How can I define a string without defining its size?

char buffer[100]; 
int offset = sprintf (buffer, "%d plus %d is %d", 5, 3, 5+3);
offset += sprintf (buffer + offset, " and %d minus %d is %d", 6, 3, 6-3);
offset += sprintf (buffer + offset, " even more");
printf ("[%s]",buffer);  
Enchantres
  • 853
  • 2
  • 9
  • 22
  • use `malloc` to get a pointer to a char buffer of arbitrary size. `char* buffer = malloc(sizeof(char)*MY_SIZE);` – Raildex Aug 19 '21 at 12:06
  • 1
    If you need to grow an array obtained from `malloc()`, use [`realloc()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/realloc.html) – pmg Aug 19 '21 at 12:08
  • Does this answer your question? [Is it possible to modify a string of char in C?](https://stackoverflow.com/questions/1011455/is-it-possible-to-modify-a-string-of-char-in-c) – Iguananaut Aug 19 '21 at 12:14
  • 1
    `snprintf()` (C99 onwards: beware Windows implementation which is non-conformant) reports about the size needed to avoid buffer overflows. – pmg Aug 19 '21 at 12:22
  • My bad: `snprintf()` on Windows implementation **used to be** (before 2019??) non-conformant. – pmg Aug 19 '21 at 12:30
  • You can use `snprintf()` with `len==0` first, `snprintf()` will report the required size and not print anything, then you know how much memory you need to alloc for it. – 12431234123412341234123 Aug 19 '21 at 13:44

2 Answers2

4

This is a fundamental aspect of C. C never does automatic management of dynamically-constructed strings for you — this is always your responsibility.

Here is an outline of four different techniques you might use. You can ask additional questions about any of these that aren't clear.

  1. Run through your string-construction process twice. Make one pass to collect the lengths of all the substrings, then call malloc to allocate a buffer of the computed size, then make a second pass to actually construct your string.

  2. Allocate a smallish (or empty) initial buffer with malloc, and then, each time you're about to append a new substring to it, check the buffer's size, and if necessary grow it bigger using realloc. (In this case I always use three variables: (1) pointer to buffer, (2) allocated size of buffer, (3) number of characters currently in buffer. The goal is to always keep (2) ≥ (3).)

  3. Allocate a dynamically-growing "memstream" and use fprintf or the like to "print" to it. This is an ideal technique, although memstreams are not standard and not supported on all platforms, and dynamically-allocating memstreams are even more exotic and less common. (It's possible to write your own, but it's a lot of work.) You can open a fixed-size memstream using fmemopen (although this is not what you want), and you can open the holy grail, a dynamically-allocating memstream (which is what you want) using open_memstream, if you have it. Both are documented on this man page. (This technique is analogous to stringstream in C++.)

  4. The "better to beg forgiveness than ask permission" technique. You can allocate a buffer which you're pretty sure is amply big enough, then blindly stuff all your substrings into it, then at the very end call strlen on it and, if you guessed wrong and the string is longer than the buffer you allocated, print a big scary noisy error message and abort. This is a blunt and risky technique, not one you'd use in a production program. It's possible that when you overflow the buffer, you damage things in a way that causes the program to crash before it has a chance to perform its belated check-and-maybe-exit step at all. If you used this technique at all, it would be considerably "safer" (that is, considerably less likely to prematurely crash before the check) if you allocated the buffer using malloc, than if you declared it as an ordinary, fixed-size array (whether static or local).

Personally, I've used all four of these. Out in the rest of the world, numbers 1 and 2 are both in common use by just about everybody. Comparing them: number 1 is simple and a bit easier but has an uncomfortable amount of code replication (and might therefore be brittle if new strings are added later); number 2 is more robust but obviously requires you to be comfortable with the way realloc works (and this technique can be less than robust in its own way, if you've got any auxiliary pointers into your buffer which need to be ponderously relocated each time realloc is called).

Number 3 is an "exotic" technique: theoretically almost ideal, but definitely more sophisticated and necessitating some extra support, since nothing like open_memstream is standard. And number 4 is obviously a risky and inherently not reliable technique, which you'd use — if at all — only in throwaway or prototype code, never in production.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • 1
    I'm not sure I would characterize this as a "limitation". It is perhaps a fundamental aspect of C, or a fundamental characteristic, but it's not a limitation. A fundamental limitation of lisp is that it does not enable faster than light message transfer. A fundamental limitation of scala is that it cannot run without hardware to carry out the instructions. – William Pursell Aug 19 '21 at 14:06
  • @WilliamPursell Fair point. What I was getting at is that you can't sling strings around as first-class types as you can in, for example, C++ or BASIC. You're right: "aspect" is a better word. – Steve Summit Aug 19 '21 at 14:31
  • I use `obstack` for growing buffers, like a JSON document I'm receiving from a server. – Cheatah Aug 19 '21 at 15:51
  • @Cheatah Never heard of that particular implementation. Thanks! – Steve Summit Aug 19 '21 at 16:13
2

You can use the snprintf function with NULL for the first argument and 0 for the second get the size that a formatted string would be. You can then allocate the space dynamically and call snprintf again to actually build the string.

char *buffer = NULL; 
int len, offset = 0;

len = snprintf (NULL, 0, "%d plus %d is %d", 5, 3, 5+3);
buffer = realloc(buffer, offset + len + 1);
offset = sprintf (buffer + offset, "%d plus %d is %d", 5, 3, 5+3);

len = snprintf (NULL, 0, " and %d minus %d is %d", 6, 3, 6-3);
buffer = realloc(buffer, offset + len + 1);
offset += sprintf (buffer + offset, " and %d minus %d is %d", 6, 3, 6-3);

len = snprintf (NULL, 0, " even more");
buffer = realloc(buffer, offset + len + 1);
offset += sprintf (buffer + offset, " even more");

printf ("[%s]",buffer);

Note that this implementation omits checking on realloc and snprintf for brevity. It also repeats the format string and arguments. The following function addresses these shortcomings:

int append_buffer(char **buffer, int *offset, const char *format, ...)
{
    va_list args;
    int len;

    va_start(args, format);
    len = vsnprintf(NULL, 0, format, args);
    if (len < 0) {
        perror("vsnprintf failed");
        return 0;
    }
    va_end(args);

    char *tmp = realloc(*buffer, *offset + len + 1);
    if (!tmp) {
        perror("realloc failed");
        return 0;
    }
    *buffer = tmp;

    va_start(args, format);
    *offset = vsprintf(*buffer + *offset, format, args);
    if (len < 0) {
        perror("vsnprintf failed");
        return 0;
    }
    va_end(args);

    return 1;
}

Which you can then call like this:

char *buffer = NULL;
int offset = 0;

int rval;
rval = append_buffer(&buffer, &offset, "%d plus %d is %d", 5, 3, 5+3);
if (!rval) return 1;
rval = append_buffer(&buffer, &offset, " and %d minus %d is %d", 6, 3, 6-3);
if (!rval) return 1;
rval = append_buffer(&buffer, &offset, " even more");
if (!rval) return 1;

printf ("[%s]",buffer);
free(buffer);
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Please make a function for that. Don't write the format string twice, it violates DRY and it is likely you forget to change one when you forget to change the other. And don't forget to check for error values (check if `len=>0` and check if `!!buffer`) – 12431234123412341234123 Aug 19 '21 at 13:46