55

When converting an int like so:

char a[256];
sprintf(a, "%d", 132);

what's the best way to determine how large a should be? I assume manually setting it is fine (as I've seen it used everywhere), but how large should it be? What's the largest int value possible on a 32 bit system, and is there some tricky way of determining that on the fly?

11
  • Of course, if using C++ is an option instead then you can just use std::string and std::stringstream to accomplish what you want without even thinking about memory requirements. But that really depends. I know that the question is for C but maybe this might be useful anyway. Commented Oct 13, 2010 at 0:33
  • 8
    @Robert: if using Python is an option instead, then you can use str ;-p Commented Oct 13, 2010 at 0:35
  • 3
    @Robert: It's for a uni assignment. I hated C prior to this unit. Now I love the simplicity. It's unforgiving, but very satisfying, with a big learning curve given I usually dabble in Python/managed langs. Commented Oct 13, 2010 at 1:11
  • 3
    @Robert: I would argye that if you are having to think about this sort of issue, no language other than C or assembly could possibly meet your requirements. Any other language will have monstrous difficult-to-predict stack usage, heap fragmentation, etc. Commented Oct 13, 2010 at 2:19
  • 4
    In the GNU world you have asprintf, which will internally malloc the needed amount of memory. Commented Apr 1, 2013 at 20:39

8 Answers 8

117

Some here are arguing that this approach is overkill, and for converting ints to strings I might be more inclined to agree. But when a reasonable bound for string size cannot be found, I have seen this approach used and have used it myself.

int size = snprintf(NULL, 0, "%d", 132);
char * a = malloc(size + 1);
sprintf(a, "%d", 132);

I'll break down what's going on here.

  1. On the first line, we want to determine how many characters we need. The first 2 arguments to snprintf tell it that I want to write 0 characters of the result to NULL. When we do this, snprintf won't actually write any characters anywhere, it will simply return the number of characters that would have been written. This is what we wanted.
  2. On the second line, we are dynamically allocating memory to a char pointer. Make sure and add 1 to the required size (for the trailing \0 terminating character).
  3. Now that there is enough memory allocated to the char pointer, we can safely use sprintf to write the integer to the char pointer.

Of course you can make it more concise if you want.

char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1);
sprintf(a, "%d", 132);

Unless this is a "quick and dirty" program, you always want to make sure to free the memory you called with malloc. This is where the dynamic approach gets complicated with C. However, IMHO, if you don't want to be allocating huge char pointers when most of the time you will only be using a very small portion of them, then I don't think this is bad approach.

Sign up to request clarification or add additional context in comments.

5 Comments

Actually, you could often just use alloca instead of malloc. And if the resulting code is still too bloaty, make a macro for it.
How portable is alloca? It certainly isn't ANSI C.
Surely the portable C99 alternative to alloca is just to use a variable-length array? int size = ...; char a[size+1]; sprintf(...?
Don't forget to handle the case where malloc() returns NULL, unless you don't care if your program crashes or is insecure. This is especially true if the size depends on input from outside the program.
Definitely +1, I don't get why this is not the accepted answer. It's exactly what the man page explains.
23

It's possible to make Daniel Standage's solution work for any number of arguments by using vsnprintf which is in C++11/C99.

int bufferSize(const char* format, ...) {
    va_list args;
    va_start(args, format);
    int result = vsnprintf(NULL, 0, format, args);
    va_end(args);
    return result + 1; // safe byte for \0
}

As specified in c99 standard, section 7.19.6.12 :

The vsnprintf function returns the number of characters that would have been written had n been sufficiently large, not counting the terminating null character, or a negative value if an encoding error occurred.

4 Comments

It`s pure C99 to.
Thanks. Didn't even notice. Updated my answer.
_vscprintf is better solution. Look at - stackoverflow.com/a/9369242/987850
_vscprintf is microsoft only.
15

The max possible number of bits in an int is CHAR_BIT * sizeof(int), and a decimal digit is "worth" at least 3 bits, so a loose upper bound on the space required for an arbitrary int is (CHAR_BIT * sizeof(int) / 3) + 3. That +3 is one for the fact that we rounded down when dividing, one for the sign, one for the nul terminator.

If by "on a 32 bit system" you mean that you know int is 32 bits, then you need 12 bytes. 10 for the digits, one for the sign, one for the nul terminator.

In your specific case, where the int to be converted is 132, you need 4 bytes. Badum, tish.

Where fixed-size buffers can be used with a reasonable bound, they are the simpler option. I not-so-humbly submit that the bound above is reasonable (13 bytes instead of 12 for 32 bit int, and 23 bytes instead of 21 for 64 bit int). But for difficult cases, in C99 you could just call snprintf to get the size, then malloc that much. That's overkill for such a simple case as this.

11 Comments

Image
Using malloc for this is ridiculous. It overcomplicates your code by adding a failure case you have to check -- and what do you do if it fails?!? Simply use a correctly sized buffer like you explained how to do.
@R: Using malloc is not ridiculous if the person writing the code is interested in learning or knowing the underpinnings of the language itself. Telling people to just use std::string because all the hard work has already done is ignorant to this point; i.e. wanting to know how things work under the hood so to speak. Maybe the original poster wants to know how std::string does what it does?
@Eric: this question is about C, nothing to do with std::string.
This is dangerous - a classic potential buffer overrun scenario. The exact output of the printf family of functions depends on the locale. For example, a locale may set the 'thousands' separator.
@Brett: you had me going for a minute there, but of course %d doesn't use the thousands separator. %'d would, but that's not the question.
|
7

I see this conversation is a couple of years old, but I found it while trying to find an answer for MS VC++ where snprintf cannot be used to find the size. I'll post the answer I finally found in case it helps anyone else:

VC++ has the function _scprintf specifically to find the number of characters needed.

Comments

2

If you're printing a simple integer, and nothing else, there's a much simpler way to determine output buffer size. At least computationally simpler, the code is a little obtuse:

char *text;
text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2);

log10(value) returns the number of digits (minus one) required to store a positive nonzero value in base 10. It goes a little off the rails for numbers less than one, so we specify abs(), and code special logic for zero (the ternary operator, test ? truecase : falsecase). Add one for the space to store the sign of a negative number (val < 0), one to make up the difference from log10, and another one for the null terminator (for a grand total of 2), and you've just calculated the exact amount of storage space required for a given number, without calling snprintf() or equivalents twice to get the job done. Plus it uses less memory, generally, than the INT_MAX will require.

If you need to print a lot of numbers very quickly, though, do bother allocating the INT_MAX buffer and then printing to that repeatedly instead. Less memory thrashing is better.

Also note that you may not actually need the (double) as opposed to a (float). I didn't bother checking. Casting back and forth like that may also be a problem. YMMV on all that. Works great for me, though.

2 Comments

Let us say that your integer is INT_MIN. Then abs(val) is INT_MIN, log10 applied to it returns NaN, and converting NaN to int is undefined behavior. Besides, if you are printing 64-bit integers, the largest of them are not representable exactly as double.
@Pascal Cuoq -- Interesting quirk, thanks for that. I guess the solution isn't really complete without checking if we're trying to allocate storage for the string representation of INT_MIN, but I'm feeling lazy, so I'll leave that as an exercise for anyone who actually cares to do it. -- P.S. "undefined behavior" is an understatement. In GCC, printf("%d\n", (int)log10((double)abs(-2147483647 - 1))); spits out "-2147483648" instead of the expected "9" -- how odd.
1

First off, sprintf is the devil. If anything, use snprintf, or else you risk trashing memory and crashing your app.

As for the buffer size, it's like all other buffers - as small as possible, as big as necessary. In your case, you have a signed integer, so take the largest possible size, and feel free to add a little bit of safety padding. There is no "standard size".

It's also not dependent on what system you're running on. If you define the buffer on the stack (like in your example), it depends on the size of the stack. If you created the thread yourself, then you determined the stack size yourself, so you know the limits. If you are going to expect recursion or a deep stack trace, then you need to extra careful as well.

6 Comments

Thank you. snprintf it is then.
Image
There is no danger to using sprintf with a correctly sized buffer. For numeric output, sizing the buffer is easy. See Steve's answer.
@R.. Assuming another thread doesn't clobber some of the input behind your back...
Image
@tc.: In that case snprintf would not help you. Unsynchronized access to data is always undefined behavior. It's equally possible that snprintf could overflow if the string size changes out from under it.
@R.. While it's not guaranteed to help you, I can think of obvious situations and implementations where snprintf() would at least stop it from crashing.
|
1

Its good that you are worried about buffer size. To apply that thought in code, I would use snprintf

snprintf( a, 256, "%d", 132 );

or

snprintf( a, sizeof( a ), "%d", 132 );  // when a is array, not pointer

1 Comment

Image
snprintf only solves half the problem. If you're not sure your buffer is big enough, you need to use snprintf to test the necessary size or make sure your code does not have bugs when the output gets truncated. Steve's answer is a lot better.
0

Using Visual Studio 2022 with its C++ implementation I noticed either sprintf() or snprintf() as well as the vsprintf(), vsnprintf() implementations do not behave like the answers here suggest it should -- that is, I can't query the length of the would-be buffer with them.

Instead, I need to call a shady _scprintf() or _vscprintf().

So while the answers above are probably right, I guess there's no "standard" or "portable" way to do this. The code should have some #ifdef to handle whether it's being compiled by GNU or MSBuild compilers.

That said, the equivalent to the currently best voted answer from Daniel Standage for Visual C++ would be:

  int size = _scprintf("%d", 132);
  char * a = malloc(size + 1);
  sprintf(a, "%d", 132);

Then maybe a "portable" (may I dare call it "standard"?) approach would be:

#ifdef _MSC_VER
  int size = _scprintf("%d", 132);
#else
  int size = snprintf(NULL, 0, "%d", 132);
#endif
  char * a = malloc(size + 1);
  sprintf(a, "%d", 132);

Then we could hope for the best, that any other c compiler pre-processing this source would support the best voted answer. :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.