2

I want to store a long value (LONG_MAX in my test program) in a dynamically allocated string, but I'm confused how much memory I need to allocate for the number to be displayed in the string.

My fist attempt:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

int main(void)
{
    char *format = "Room %lu somedata\n";
    char *description = malloc(sizeof(char) * strlen(format) + 1);

    sprintf(description, format, LONG_MAX);

    puts(description);

    return 0;
}

Compiled with

gcc test.c

And then running it (and piping it into hexdump):

./a.out | hd 

Returns

00000000  52 6f 6f 6d 20 39 32 32  33 33 37 32 30 33 36 38  |Room 92233720368|
00000010  35 34 37 37 35 38 30 37  20 62 6c 61 62 6c 61 0a  |54775807 blabla.|
00000020  0a                                                |.|
00000021

Looking at the output, it seems my memory allocation of sizeof(char) * strlen(format) + 1 is wrong (too less memory allocated) and it works more accidentally?

What is the correct amount to allocate then?

My next idea was (pseudo-code):

sizeof(char) * strlen(format) + strlen(LONG_MAX) + 1

This seems too complicated and pretty non-idomatic. Or am I doing something totally wrong?

Max
  • 15,693
  • 14
  • 81
  • 131
  • Obviously `strlen("Room %lu somedata\n")` is less than that same string where `%u` is replaced with 19 digits... (hey - what if there is a minus in there as well?) – Jongware May 22 '15 at 13:41
  • @Jongware I understand now, I feel also a bit stupid asking this question, as its pretty obvious actually … albeit I would have forgotten about the minus though. :-) – Max May 22 '15 at 13:50
  • 1
    Many of the answers here dwell on `LONG_MAX`. Unfortunately, the format is `"Room %lu somedata\n"` which is looking for an `unsigned long` and not the `signed long LONG_MAX`. This is one of the error paths of `sprintf()`: looking at what is passed to it, rather than looking at what `sprintf()` interprets. Considering _locale_ and other features of `sprintf()`, better to be generous in buffer allocation (suggest 2x) and use `snprintf()` instead. – chux - Reinstate Monica May 22 '15 at 14:13

12 Answers12

3

You are doing it totally wrong. LONG_MAX is an integer, so you can't call strlen (). And it's not the number that gives the longest result, LONG_MIN is. Because it prints a minus character as well.

A nice method is to write a function

char* mallocprintf (...)

which has the same arguments as printf and returns a string allocated using malloc with the exactly right length. How you do this: First figure out what a va_list is and how to use it. Then figure out how to use vsnprintf to find out how long the result of printf would be without actually printing. Then you call malloc, and call vsnprintf again to produce the string.

This has the big advantage that it works when you print strings using %s, or strings using %s with some large field length. Guess how many characters %999999d prints.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Thank you for your answer, I learned a lot. Also thank you for your pointers how to learn more. I have one complaint though: "You are doing it totally wrong." – I'm aware that I can't call strlen on LONG_MAX, that is why I wrote in the question "pseudo-code". – Max May 22 '15 at 13:47
  • 1
    Note: `LONG_MAX/LONG_MIN` are not the values to use with`"%lu"`. Suggest `ULONG_MAX`. – chux - Reinstate Monica May 22 '15 at 14:03
3

You can use snprintf() to figure out the length without worrying about the size of LONG_MAX.

When you call snprintf with NULL string, it'll return a number of bytes that would have been required if it was write into the buffer and then you know exactly how many bytes are required.

    char *format = "Room %lu somedata\n";

    int len = snprintf(0, 0, format, LONG_MAX); // Returns the number of 
                               //bytes that would have been required for writing.
    char *description = malloc( len+1 );

    if(!description) 
    { 
      /* error handling */
    }

    snprintf(description, len+1, format, LON_MAX);
P.P
  • 117,907
  • 20
  • 175
  • 238
3

Convert the predefined constant numeric value to a string, using macro expansion as explaned in convert digital to string in macro:

#define STRINGIZER_(exp)   #exp
#define STRINGIZER(exp)    STRINGIZER_(exp)

(code courtesy of Whozcraig). Then you can use

int max_digit = strlen(STRINGIZER(LONG_MAX))+1;

or

int max_digit = strlen(STRINGIZER(LONG_MIN));

for signed values, and

int max_digit = strlen(STRINGIZER(ULONG_MAX));

for unsigned values.

Since the value of LONG_MAX is a compile-time, not a run-time value, you are ensured this writes the correct constant for your compiler into the executable.

Community
  • 1
  • 1
Jongware
  • 22,200
  • 8
  • 54
  • 100
  • I would have to say that this is the preferred method, as it is done in compile-time and also portable. Using sprintf etc is needlessly slow and burdensome. – Lundin May 22 '15 at 14:05
  • @Lundin: but see [BlueMoon's](http://stackoverflow.com/a/30398613/2564301) for a perfectly good solution. – Jongware May 22 '15 at 14:06
  • 1
    Note: OP is using `"%lu"` and not `"%ld"`, so `LONG_MAX/LONG_MIN` are no so important as `ULONG_MAX`. Focusing on the arguments and not the format leads to this problem. – chux - Reinstate Monica May 22 '15 at 14:07
  • 1
    @Jongware I wouldn't say so, any call to printf family of functions consumes a lot of time. There is no way it will out-perform strlen(), and your strlen() version is also the most readable one, which alone makes it the best solution. – Lundin May 22 '15 at 14:09
  • @Lundin: thanks! I wonder if optimizing compilers would recognize this `strlen` call on a compile-time constant string (!) as a constant in itself. – Jongware May 22 '15 at 14:11
  • @Jongware Actually, based on your answer I came up with an even better idea, use sizeof() and get no run-time calculation what-so-ever. I posted this as an answer, based on yours. If you wish to edit your answer with a sizeof solution, feel free to do so and I'll delete my answer. – Lundin May 22 '15 at 14:18
  • @Lundin While printf family functions can be slower, using snprintf is a more generically applicable solution for *any* string formatting rather just `LONG_MAX`. If number of digits of `LONG_MAX` is the only concern, then OP can simply declare such as `char str[256];`. Besides, it's hard to avoid any printf functions in any reasonably big project despite them being slower and also `snprintf` is safer way than `strlen/strcpy` if security matters the most. – P.P May 22 '15 at 15:04
  • @BlueMoon Working exclusively with embedded systems, I've managed to avoid printf functions for the past 10 years or so, no matter how large the project was. [strlen/strcpy are perfectly safe functions](http://stackoverflow.com/questions/23486938/c4996-function-unsafe-warning-for-strcpy-but-not-for-memcpy/23490019#23490019). If you don't sanity check the data of _any_ function before passing it as parameter, then of course anything can happen. That's not strlen/strcpy's fault, they are C standard functions, not C programming teachers. – Lundin May 22 '15 at 18:01
  • STRINGIZER doesn't actually work here! On my system, I end up with "(0x7fffffffffffffffL * 2UL + 1UL)" – Luke-Jr Oct 26 '21 at 18:52
1

To allocate enough room, consider worst case

// Over approximate log10(pow(2,bit_width))
#define MAX_STR_INT(type) (sizeof(type)*CHAR_BIT/3 + 3)

char *format = "Room %lu somedata\n";
size_t n = strlen(format) + MAX_STR_INT(unsigned long) + 1;
char *description = malloc(n);
sprintf(description, format, LONG_MAX);

Pedantic code would consider potential other locales

snprintf(description, n, format, LONG_MAX);

Yet in the end, recommend a 2x buffer

char *description = malloc(n*2);
sprintf(description, format, LONG_MAX);

Note: printing with specifier "%lu" ,meant for unsigned long and passing a long LONG_MAX in undefined behavior. Suggest ULONG_MAX

sprintf(description, format, ULONG_MAX);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

With credit to the answer by @Jongware, I believe the ultimate way to do this is the following:

#define STRINGIZER_(exp)   #exp
#define STRINGIZER(exp)    STRINGIZER_(exp)

const size_t LENGTH = sizeof(STRINGIZER(LONG_MAX)) - 1;

The string conversion turns it into a string literal and therefore appends a null termination, therefore -1.

And not that since everything is compile-time constants, you could as well simply declare the string as

const char *format = "Room " STRINGIZER(LONG_MAX) " somedata\n";
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • (Good enough as an answer on its own. Would you add that `sizeof` is to return the *string* length here, or is that Blatantly Obvious to everyone but me, accustomed as I am to mean "sizeof(a_string) == sizeof(char *)?) – Jongware May 22 '15 at 14:23
  • Good answer - yes. Ultimate - well... This approach relies on 1) the matching `printf()` specifier `"%ld"` was used. It wasn't, instead OP used `"%lu"`. So per spec this is UB. 2) `printf()` is not in some _interesting_ locale that say injects `","` and tries to print "2,147,483,647". IAC, `sprintf()` is a challenging function to supply a right-sized buffer. – chux - Reinstate Monica May 22 '15 at 14:56
  • 1
    @Jongware String literals are arrays so if you take the sizeof them, you get the string length + null termination. – Lundin May 22 '15 at 17:52
  • @chux I'm sure the average person can replace size_t with unsigned long without me telling them to do so. But since it is all compile-time constants, I don't quite see why you would need to use printf-esque functions for this any longer. Neither do you need malloc. – Lundin May 22 '15 at 17:58
  • It is not a `size_t` versus `unsigned long` issue - not sure why you focus on `size_t`. It is how much space for a `unsigned long` even though code attempts to uses the wrong worst case macro `LONG_MAX` to determine it? As OP wants to store the a string representation of some integer, some "printf-esque" method is the standard way to do so. I concur that some standard less heavily coded function other than `sprintf()` should exist for integer to string conversion. – chux - Reinstate Monica May 22 '15 at 18:53
0

You cannot use the format. You need to observer

LONG_MAX = 2147483647 = 10 characters
"Room  somedata\n" = 15 characters

Add the Null = 26 characters

so use

malloc(26)

should suffice.

Ed Heal
  • 59,252
  • 17
  • 87
  • 127
0

Well, if long is a 32-bit on your machine, then LONG_MAX should be 2147483647, which is 10 characters long. You need to account for that, the rest of your string, and the null character.

Keep in mind that long is a signed value with maximum value of LONG_MAX, and you are using %lu (which is supposed to print an unsigned long value). If you can pass a signed value to this function, then add an additional character for the minus sign, otherwise you might use ULONG_MAX to make it clearer what your limits are.

If you are unsure which architecture you are running on, you might use something like:

// this should work for signed 32 or 64 bit values
#define NUM_CHARS ((sizeof(long) == 8) ? 21 : 11)

Or, play safe and simply use 21. :)

vgru
  • 49,838
  • 16
  • 120
  • 201
0

You have to allocate a number of char equal to the digits of the number LONG_MAX that is 2147483647. The you have to allocate 10 digit more.

in your format string you fave

  1. Room = 4 chars
  2. somedata\n = 9
  3. spaces = 2
  4. null termination = 1

The you have to malloc 26 chars

If you want to determinate runtime how man digit your number has you have to write a function that test the number digit by digit:

  while(n!=0)
  {
      n/=10;             /* n=n/10 */
      ++count;
  }

Another way is to store temporary the sprintf result in a local buffer and the mallocate strlen(tempStr)+1 chars.

LPs
  • 16,045
  • 8
  • 30
  • 61
0

Here, your using strlen(format) for allocation of memory is bit problematic. it will allocate memory considering the %lu lexicographically, not based on the lexicographical width of the value that can be printed with %lu.

You should consider the max possible value for unsigned long,

ULONG_MAX             4294967295

lexicographically 10 chars.

So, you've to allocate space for

  • The actual string (containing chars), plus
  • 10 chars (at max), for the lexicographical value for %lu , plus
  • 1 char to represnt - sign, in case the value is negative, plus
  • 1 null terminator.
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • "It will allocate memory considering the %lu lexicographically" – thanks, that really made me wonder. I understand now. – Max May 22 '15 at 13:45
  • Can you have fewer italicized "lexicographical" variants? :) – unwind May 22 '15 at 13:50
0

Usually this is done by formatting into a "known" large enough buffer on the stack and then dynamically allocated whatever is needed to fit the formatted string. i.e.:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

int main(void)
{
    char buffer[1024];

    sprintf(buffer, "Room %lu somedata\n", LONG_MAX);
    char *description = malloc( strlen( buffer ) + 1 );

    strcpy( description, buffer );
    puts(description);

    return 0;
}
Brian Sidebotham
  • 1,706
  • 11
  • 15
0

Use the following code to calculate the number of characters necessary to hold the decimal representation of any positve integer:

#include <math.h>

...

size_t characters_needed_decimal(long long unsigned int llu)
{
   size_t s = 1;

   if (0LL != llu)
   {
     s += log10(llu);
   }

   return s;
}

Mind to add 1 when using a C-"string" to store the number, as C-"string"s are 0-terminated.

Use it like this:

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

size_t characters_needed_decimal(long long unsigned int);

int main(void)
{
  size_t s = characters_needed_decimal(LONG_MAX);
  ++s; /* 1+ as we want a sign */

  char * p = malloc(s + 1); /* add one for the 0-termination */
  if (NULL == p)
  {
    perror("malloc() failed");
    exit(EXIT_FAILURE);
  }

  sprintf(p, "%ld", LONG_MAX);
  printf("LONG_MAX = %s\n", p);

  sprintf(p, "%ld", LONG_MIN);
  printf("LONG_MIN = %s\n", p);

  free(p);

  return EXIT_SUCCESS;
}
alk
  • 69,737
  • 10
  • 105
  • 255
0

Safest:

Rather than predict the allocation needed, uses asprintf(). This function allocates memory as needed.

char *description = NULL;
asprintf(&description, "Room %lu somedata\n", LONG_MAX);

asprintf() is not standard C, but is common in *nix and its source code is available to accommodate other systems.

Why Use Asprintf?
apple
android

Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256