Question

When converting an integer to text, typically I create a big buffer to use with sprintf() to hold any potential result.

char BigBuffer[50];
sprintf(BugBuffer, "%d", SomeInt);

I'd like to more space efficient and certainly portable, so instead of 50, found the alternative:
(sizeof(integer_type)*CHAR_BIT*0.302) + 3

// 0.0302 about log10(2)
#define USHORT_DECIMAL_BUFN ((size_t) (sizeof(unsigned short)*CHAR_BIT*0.302) + 3)
#define INT_DECIMAL_BUFN    ((size_t) (sizeof(int)           *CHAR_BIT*0.302) + 3)
#define INTMAX_DECIMAL_BUFN ((size_t) (sizeof(intmax_t)      *CHAR_BIT*0.302) + 3)

int main() {
    char usbuffer[USHORT_DECIMAL_BUFN];
    sprintf(usbuffer, "%hu", USHRT_MAX);
    printf("Size:%zu Len:%zu %s\n", sizeof(usbuffer), strlen(usbuffer), usbuffer);

    char ibuffer[INT_DECIMAL_BUFN];
    sprintf(ibuffer, "%d", INT_MIN);
    printf("Size:%zu Len:%zu %s\n", sizeof(ibuffer), strlen(ibuffer), ibuffer);

    char imbuffer[INTMAX_DECIMAL_BUFN];
    sprintf(imbuffer, "%" PRIdMAX, INTMAX_MIN);
    printf("Size:%zu Len:%zu %s\n", sizeof(imbuffer), strlen(imbuffer), imbuffer);
    return 0;
}

Size:7 Len:5 65535
Size:12 Len:11 -2147483648
Size:22 Len:20 -9223372036854775808

So the questions are:
1 Is there a problem with the alternative equation?
2 What better solution? - as this alternative is a tad wasteful and looks overly complicated.

[Edit Answer]

Answers provide 3 thoughtful approaches:
1 Use buffer[max size for type] (Answer selected)
2 asprintf()
3 snprintf()

1 The compile time max buffer size using equation (sizeof(integer_type)*CHAR_BIT*0.302) + 3 was not broken nor improved. The impact of <locale.h> was researched as suggested by @paddy and no locale settings affected integer conversions %d %x %u %i. It was found a slight improvement may be made to the equation if the type is known to be signed or unsigned (below). @paddy caution about "more conservative" is good advice.

2 asprintf() is really a good all-purpose solution, but not portable. Maybe in post-C11?

3 snprintf(), although standard, has known consistent implementation issues when the supplied buffer is undersized. This implies calling it with an over-sized buffer and then generating a right-size buffer. @jxh suggested a thread safe global scratch buffer to form the answer with a local right-sized buffer. This novel approach deserves consideration which I may use, but the original question focused more on determining before the s(n)printf() call a conservative buffer size.

signed ((sizeof(integer_type)*CHAR_BIT-1)*0.302) + 3
unsigned (sizeof(integer_type)*CHAR_BIT*0.302) + 2
*28/93 may be used in lieu of *0.302.

Was it helpful?

Solution

It looks good to me. You've rounded the decimal up, added an extra character for the negative sign and the null, and one extra for good measure. I don't think you need to worry about numbers coming out longer if you're not using features from <locale.h>.

My question is about what you intend to do with these. Are you simply constructing them on the stack, or are you putting lots of them into memory?

With temporary arrays on the stack, you normally wouldn't fuss over a few bytes as it's unlikely to affect cache performance. It certainly isn't going to blow out your memory.

If you are planning to store lots of these, you may want to consider pooling. However, you would need to consider the memory overhead of pooling. The very nature of a pool means you reserve more memory than you are going to use. And if compiling 64-bit, your pointers are 8 bytes. If most of your numbers are 4 characters long, then the 8-byte pointer plus the 5 bytes of storage for each number would negate any possible benefits, except perhaps for 64-bit numbers.

These are just my thought processes. It seems to me that you have trimmed the fat nicely. I tend to be a little more conservative, but that may be mostly paranoia. Keeping it simple is usually the way to go, and over-thinking can be a trap. If you are over-thinking then consider the reasons why, and decide whether it's a problem that actually requires that much thought.

OTHER TIPS

asprintf() is handy, it takes a char ** and uses malloc() to get the needed space so you need to free() it later.

No need to worry about how much space you need.

int asprintf(char **ret, const char *format, ...); 

char *p
asprintf(&p, "%XXXX", ...); 
:
:
free(p);

Here's a scheme, expanding my earlier comment. You use the INTMAX_DECIMAL_BUFN as your worst case buffer size, and use it for printing with snprintf(). The value returned by snprintf() is used to declare a VLA that matches exactly the array size needed for the printed string, and that string is copied to the VLA.

#define INTMAX_DECIMAL_BUFN ((size_t) (sizeof(intmax_t)*CHAR_BIT*0.302) + 3)

char numstr[INTMAX_DECIMAL_BUFN];

int main () {
    int n = snprintf(numstr, sizeof(numstr), "%hu", USHRT_MAX);
    char usbuffer[n+1];
    strcpy(usbuffer, numstr);
    printf("Size:%zu Len:%zu %s\n", sizeof(usbuffer), strlen(usbuffer), usbuffer);
}

If thread safety is an issue, the numstr variable could be made thread local (with C.11's _Thread_local, or some compiler specific extension similar to GCC's __thread).

The value of this solution depends on whether the savings in stack space is worth the extra compute to perform the strcpy(). If most of your numbers using the larger integer types actually take on values much smaller than the max, then this technique could provide you with significant savings (depending on how many arrays you are creating).

These are fine.

I designed the original snprintf() function (in *BSD, that eventually made it into C99) to return the number of characters that would have been printed, had the buffer been large enough. If you have a conforming snprintf() you can do the printing twice, with the first telling you how much space to allocate (you must add one for the terminating '\0' of course). This has two obvious drawbacks: it has to do the formatting twice, and it introduces the possibility of synchronization issues, where the first call changes something (e.g., writing via %n directive) so that the second call produces different output.

Unfortunately, there are non-compliant snprintf() implementations where this does not work anyway. [Edit: it works for the usage in jxh's answer, where you supply a large buffer; the failing case is when you supply a too-small buffer to find out how much room you need.]

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top