Home > Mobile >  Can I assign a printed output into a variable?
Can I assign a printed output into a variable?

Time:11-18

I am figuring out if I can make a variable out of a printed output is possible. For example

#include <stdio.h>

void main()
{
    char n[4] = "2445";
    int i;

    for (i = 0; i <= 4; i  )
    {
        printf("%c", n[i]   1);
    }
}

the output is 3556

Can I make a variable out of the output?

CodePudding user response:

void main(void)
{
    char n[4]="2445";
    char result[5];
    size_t i;

    for(i = 0; i < sizeof(n); i  )
    {
        result[i] = n[i]   1; 
    }
    result[i] = 0;

    printf("result = `%s`\n", result);
}

CodePudding user response:

If you just want to output single characters, then putting data in a char buffer, as the other answer suggests, is the right answer. It is easy, it is fast, it is nothing but goodness.

If, however, you need to format different types to strings, and you want to save those strings in a buffer, then you want to look at sprintf or the safer snprintf. Using this function (it is part of C99 so it should be available everywhere by now), you can write formatted strings into a buffer.

Of course, you risk writing outside the bounds of the buffer if you are not careful, which is why the snprintf function is better than sprintf, and if you write to a buffer repeatedly, you need to keep track of both where the next data should be written, and how long the buffer is. The former is easy to keep track of, because snprintf returns how much it wrote (excluding the zero termination), so you can just update a cursor with that amount each time you write.

Using snprintf with a buffer, your example program could look like this:

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

struct buf
{
    size_t cursor, len;
    char data[];
};

struct buf *new_buf(size_t len)
{
    struct buf *buf = malloc(offsetof(struct buf, data)   len);
    assert(buf); // handle allocation error
    buf->cursor = 0;
    buf->len = len;
    return buf;
}

int main(void)
{
    char n[] = "2445";
    struct buf *buf = new_buf(sizeof n);

    for (size_t i = 0; i < sizeof n; i  )
    {
        buf->cursor  =
            snprintf(buf->data   buf->cursor, // where to write
                     buf->len - buf->cursor,  // how much you can write (snprintf takes care of '\0')
                     "%c", n[i]   1);         // what to write
    }

    printf("result = `%s`\n", buf->data);

    free(buf);
    return 0;
}

After each write with snprintf, buf->data buf->cursor points at where snprintf put the zero terminal byte, so if we write the next data starting at that address, then we add one serialised data value after the next. You can modify the formatting string if you want spaces or commas or anything else between the data.

Of course, with that code, if you run out of buffer space, you have a problem. It is not that snprintf will write out of bounds (that is what the n in snprintf does better than sprintf), but it simply won't write all the data you give it. It will only write to the end of the buffer and no more.

If you want to ensure that you get all the data, you have to grow the buffer if it fills out.

We are lucky that we can get the size of a formatted string if we call it with zero for the maximum length (where we used buf->len - buf->cursor above). If we do that, and we don't need to provide a buffer if we do, then we get the length we need back. So, before we write to the buffer, we can ask for the necessary length, and then we can extend the buffer to get enough space.

#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX(A, B) (((A) > (B)) ? (A) : (B))

struct buf
{
    size_t cursor, len;
    char data[];
};

struct buf *resize_buf(struct buf *buf, size_t len)
{
    // don't allow len == 0; it will mess up doubling of lengths.
    len = len ? len : 1;

    struct buf *new_buffer = realloc(buf, offsetof(struct buf, data)   len);
    assert(new_buffer);

    // if it's a new buffer, set the cursor (otherwise it is already set)
    new_buffer->cursor = buf ? new_buffer->cursor : 0;
    new_buffer->len = len;
    return new_buffer;
}

struct buf *new_buf(size_t len)
{
    return resize_buf(0, len);
}

int main(void)
{
    char n[] = "2445";
    struct buf *buf = new_buf(0); // empty now, but we will resize

    for (size_t i = 0; i < sizeof n; i  )
    {
        // I've changed the formatting string to %d, so now you get the ASCII
        // codes plus one. They are no longer a single character long.
        // the  1 after snprintf() is for the zero sentinel '\0'
        size_t needed = snprintf(0, 0, "%d ", n[i]   1)   1;
        if (needed > buf->len - buf->cursor)
        {
            buf = resize_buf(buf, MAX(buf->len   needed, 2 * buf->len)); // double the length
        }
        buf->cursor  =
            snprintf(buf->data   buf->cursor, // where to write
                     buf->len - buf->cursor,  // how much you can write (snprintf takes care of '\0')
                     "%d ", n[i]   1);        // what to write
    }

    printf("result = `%s`\n", buf->data);

    free(buf);
    return 0;
}

I'm growing the buffer by factors of two to get a linear time growth, if you grow it only with the needed size each time it can end up with a quadratic running time. Other than that, there isn't much complicated in it; we ask for how much space we need, then we make sure that we have it by growing the buffer if needed, and then we write to the buffer the same as before.

It isn't a particularly nice solution, though. We absolutely have to make sure that the formatting string we create in the first and the second call to snprintf is the same, or we can really mess up, and the details of the buffer implementation are fundamentally exposed to the user. It is better to wrap it up in a function.

If we want to handle general formatting strings, we need a variadic function, and they can look a bit weird if you are not used to them. But they work like this: you use ... as the last argument to your function, then you can get a pointer to the additional arguments with va_start() and you need to free some data structure that holds the arguments again with va_end().

For our purposes, we do not need to do anything special with the arguments, because we can just call vsnprintf with them. That function is the equivalent of snprintf that takes these structures instead of arguments. I think vsnprintf is from C99, so you should also have it, but I admit that it is possible that it isn't there until C11 (but you should really also have that).

We need to call vsnprintf twice, and we need to set up the arguments and free them again before each call, but other than that there is nothing to it.

#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX(A, B) (((A) > (B)) ? (A) : (B))

struct buf
{
    size_t cursor, len;
    char data[];
};

struct buf *resize_buf(struct buf *buf, size_t len)
{
    // don't allow len == 0; it will mess up doubling of lengths.
    len = len ? len : 1;

    struct buf *new_buffer = realloc(buf, offsetof(struct buf, data)   len);
    assert(new_buffer);

    // if it's a new buffer, set the cursor (otherwise it is already set)
    new_buffer->cursor = buf ? new_buffer->cursor : 0;
    new_buffer->len = len;
    return new_buffer;
}

struct buf *new_buf(size_t len)
{
    return resize_buf(0, len);
}

struct buf *write_to_buf(struct buf *buf, const char *restrict fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    size_t needed = vsnprintf(0, 0, fmt, args);
    va_end(args);

    if (needed > buf->len - buf->cursor)
    {
        buf = resize_buf(buf, MAX(buf->len   needed, 2 * buf->len));
    }

    va_start(args, fmt);
    buf->cursor  =
        vsnprintf(buf->data   buf->cursor,
                  buf->len - buf->cursor,
                  fmt, args);
    va_end(args);

    return buf;
}

int main(void)
{
    char n[] = "2445";
    struct buf *buf = new_buf(0); // empty now, but we will resize

    for (size_t i = 0; i < sizeof n; i  )
    {
        buf = write_to_buf(buf, "%d ", n[i]   1);
    }

    printf("result = `%s`\n", buf->data);

    free(buf);
    return 0;
}

This should give you an idea about how to make a buffer you can write general formatting strings to. I am making no claims that this code is stable or the right kind of interface or anything, I just whipped it up rather quickly, but it should be place to start from.

  • Related