2

According to a top answer on SO this is what I should do to convert an integer to a string in C:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>

#define digits(x) ((int)((ceil(log10(abs(x)))+1)*sizeof(char)))

int main () {
    int a = 345908220;
    long b = 23094809284358;
    unsigned int c = 3456789234;
    uint8_t d = 242;
    int64_t e = -840958202029834;

    int dstr_len = digits(a) + digits(b) + digits(c) +
                   digits(d) + digits(e) + 5;

    char dstr[dstr_len];
    sprintf(dstr, "%d %ld %u %u %" PRIu64, a, b, c, d, e);

    printf("%s\n", dstr);

    return 0;
}

However, this seems ridiculously inefficient. I have to link my program to libmath, and make three math calls for every integer that I want to print. Also note that I had to add 5 to my buffer and not just 1 for the NUL terminator, by counting the number of spaces in my format string. This also seems error-prone, and could lead to a buffer overflow.

So, is there any nice, standard function, that will compute the size of my buffer for me?

I'm trying to write secure C.

Community
  • 1
  • 1
OregonTrail
  • 8,594
  • 7
  • 43
  • 58
  • 1
    You can always write your own. It's not that hard (and can be quite efficient), though thought is required to deal with the "edge" cases such as large 64-bit values. – Hot Licks Sep 05 '14 at 19:03

4 Answers4

5

If your compiler has snprintf() available, you can request the formatted buffer length and then allocate accordingly:

int dstr_len = snprintf(NULL, 0, "%d %ld %u %u %" PRIu64, a, b, c, d, e) + 1;

char dstr[dstr_len];
//
// NOTE: variable-length arrays are NOT supported in all compilers!
// A more portable solution is:
//
// char *dstr = malloc(sizeof(char) * dstr_len);

snprintf(dstr, dstr_len, "%d %ld %u %u %" PRIu64, a, b, c, d, e);

// free(dstr);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Excellent. I found `asprintf`, but it's not standard, and always requires dynamic allocation. This is standard, and doesn't strictly require dynamic allocation. Love it. – OregonTrail Sep 05 '14 at 17:59
  • Yes, hense the `+1` on the return value. – Remy Lebeau Sep 05 '14 at 18:33
  • sizeof(char) is by definition "1" so there is no need for it in the malloc, just `char * dstr = malloc(dstr_len);` is enough – John Hascall Sep 06 '14 at 03:44
  • @JohnHascall: `sizeof(char)` is indeed 1, but that does not guarantee a `char` is **1 8bit byte**. Consider systems where `CHAR_BIT > 8`, meaning `char` is more than 8bits in size. `malloc()` deals in 8bit octets, but `snprintf()` deals in `char`, so it would be more accurate to use `malloc((CHAR_BIT / 8) * dstr_len);` instead. Most developers assume `CHAR_BIT == 8`, which is true on most systems, but not all systems. – Remy Lebeau Sep 06 '14 at 05:14
  • @RemyLebeau, That is completely incorrect. malloc allocates 'bytes' where a byte is however much is required to hold a char. – John Hascall Sep 06 '14 at 20:33
2

You can use snprintf for this. From the manpage:

The functions snprintf() and vsnprintf() do not write more than size bytes (including the teriitminating null byte ('\0')). If the output was truncated due to this limit then the return value is the number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.

Thus you can call it with 0 for the size and capture the return value then allocate based on that.

dohashi
  • 1,771
  • 8
  • 12
1

You can use asprintf, which allocates a large enough output string for you.

Don't forget to free the output string, because it is dynamically allocated.

asprintf is available on Mac OSX, Linux, and BSD. The source code is available from Apple if you wish to use it on other platforms.

Example:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

int main () {
    int a = 345908220;
    long b = 23094809284358;
    unsigned int c = 3456789234;
    uint8_t d = 242;
    int64_t e = -840958202029834;

    char *dstr;
    asprintf(&dstr, "%d %ld %u %u %" PRIu64, a, b, c, d, e);
    if (dstr == NULL) {perror(NULL), exit(1);}

    printf("%s\n", dstr);
    free(dstr);

    return 0;
}
OregonTrail
  • 8,594
  • 7
  • 43
  • 58
0

Most C "runtime environments" are at most 64 bits. You could test that int is at most 64 bits (with <stdint.h> and <limits.h>) then use snprintf (not sprintf which is unsafe and deprecated) on a large enough buffer (32 bytes is more than enough for 264 in decimal).

See Posix spec on limits.h which define WORD_BIT so

#if WORD_BIT > 64
#error cannot compile on this machine
#endif

char buf[32];
snprintf(buf, sizeof(buf), "%d", a);

BTW, <stdint.h> defines several types. You might want intmax_t

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547