Is there a better way to size a buffer for printing integers?

后端 未结 5 613
执笔经年
执笔经年 2021-01-16 01:41

I want to create a buffer for sprintfing a integer (in this case an unsigned int). A simple and misguided approach would be:

char b         


        
5条回答
  •  野趣味
    野趣味 (楼主)
    2021-01-16 02:42

    The cases where something like this is needed is rare: perhaps some microcontroller code, transferring a value over some serial protocol. In such cases, using any of the printf() family of functions may increase the size of the final binary.

    (In typical C development environments, the C library is dynamically loaded, and there is absolutely no benefit in trying to avoid standard C library functions. It will not decrease the program size.)

    So, if I needed such code, I might write a header file,

    #if defined(INTTYPE) && defined (UINTTYPE) && defined (FUNCNAME)
    
    #ifndef DECIMAL_DIGITS_IN
    #define DECIMAL_DIGITS_IN(x) ((CHAR_BIT * sizeof (x) * 28) / 93 + 2)
    #endif
    
    char *FUNCNAME(const INTTYPE value)
    {
        static char buffer[DECIMAL_DIGITS_IN(value) + 1];
        char       *p = buffer + sizeof buffer;
        UINTTYPE    left = (value < 0) ? -value : value;
    
        *(--p) = '\0';
        do {
            *(--p) = '0' + (left % 10);
            left /= 10;
        } while (left > 0);
    
        if (value < 0)
            *(--p) = '-';
    
        return p;
    }
    
    #undef FUNCNAME
    #undef INTTYPE
    #undef UINTTYPE
    
    #endif
    

    and for each type I'd need, I'd use

    #define FUNCNAME int2str
    #define INTTYPE  int
    #define UINTTYPE unsigned int
    #include "above.h"
    

    In more ordinary code, the best approach is to use snprintf() to avoid buffer overruns, with the buffer size "guesstimated". For example,

    unsigned int x;
    
    char  buffer[256];
    int   len;
    
    len = snprintf(buffer, sizeof buffer, "Message with a number %u", x);
    if (len < 0 || (size_t)len >= sizeof buffer - 1) {
        /* Abort! The buffer was (almost certainly) too small! */
    } else {
        /* Success; we have the string in buffer[]. */
    }
    

    Whether buffer[] is a few dozen or even few hundred bytes larger than necessary, is completely irrelevant in typical programs. Just make it large enough, and output an error message in the error case that tells which buffer (file and function) was not long enough, so it'll be easy to fix in the unlikely case it ever is too short.


    As mentioned by dbush, asprintf() GNU extension is a viable alternative. It returns a dynamically allocated string.

    Outside of GNU systems -- and this is what I suggest OP considers, too -- one can implement their own asprintf(), using vsnprintf() (available in C99 and later C libraries, and also in POSIX.1 C libraries).

    I prefer the variant that acts like POSIX.1 getline(), i.e. takes pointers to a pointer to a dynamically allocated buffer and the size of that buffer as extra parameters, and resizes that buffer if necessary:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    size_t dynamic_printf(char **dataptr, size_t *sizeptr, const char *format, ...)
    {
        va_arg  args;
        char   *data;
        size_t  size;
        int     len;
    
        if (!dataptr || !sizeptr || !format) {
            errno = EINVAL;
            return 0;
        }
        if (!*sizeptr) {
            *dataptr = NULL;
            *sizeptr = 0;
        }
        data = *dataptr;
        size = *sizeptr;
    
        va_start(args, format);
        len = vsnprintf(data, size, format, args);
        va_end(args);
    
        if (len < 0) {
            errno = EINVAL;
            return 0;
        } else
        if ((size_t)len < size) {
            errno = 0;
            return (size_t)len;
        }
    
        /* Need to reallocate the buffer. */
        size = (size_t)len + 1;
        data = realloc(data, size);
        if (!data) {
            errno = ENOMEM;
            return 0;
        }
        *dataptr = data;
        *sizeptr = size;
    
        va_start(args, format);
        len = vsnprintf(data, size, format, args);
        va_end(args);
    
        if (len != (int)(size - 1)) {
            errno = EINVAL;
            return 0;
        }
    
        errno = 0;
        return (size_t)len;
    }
    

    The idea is that you can reuse the same dynamic buffer across several dynamic_printf() calls:

        char   *data = NULL;
        size_t  size = 0;
        size_t  len;
    
        /* Some kind of loop for example */
    
            len = dynamic_printf(&data, &size, "This is something I need in a buffer");
            if (errno) {
                /* Abort! Reason is strerror(errno) */
            } else {
                /* data is non-NULL, and has len chars in it. */
            }
    
        /* Strings are no longer used, so free the buffer */
        free(data);
        data = NULL;
        size = 0;
    

    Note that it is perfectly safe to run free(data); data = NULL; size = 0; between calls. free(NULL) does nothing, and if the buffer pointer is NULL and size zero, the function will just dynamically allocate a new buffer.

    In the worst case (when the buffer is not long enough), the function does "print" the string twice. This is perfectly acceptable, in my opinion.

提交回复
热议问题