Home > Enterprise >  C equivalent of Python format method
C equivalent of Python format method

Time:02-15

let me say at first I'm completely aware of sprintf and printf in C, But they don't meet what I need.

What I want is something like a function which does return formatted string and its parameters are just like printf. e.g.: char *formatted = format("%c%s Mund%c!", '¡', "Hola", 'o');

Has C a built-in function like that? or it should be implemented by hand? If the latter, How to implement such function?

It is worth noting that:

  1. string length is unknown
  2. I don't want to print the string

As a side note: I'll not use c , and I use mingw-64 gcc

CodePudding user response:

There isn't an equivalent function unless you make one yourself because, unlike python, strings in C are simple arrays and it's you who is responsible for allocating as much memory as you need, passing a pointer to a function, and freeing it later. That's why in functions like sprintf you need to specify an output array (and optionally a size value in variants like snprintf).

A custom implementation would be something like this (not including error checks to keep things simple):

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#define STRSIZE 256

char* my_sprintf(const char* fmt, ...) {
    /* Create a temporary buffer */
    char temp[STRSIZE];
    /* Get variadic arguments */
    va_list args;
    va_start(args, fmt);
    /* Use the variadic argument variant of snprintf
     * The return value is the number of characters that would have been written,
     * if the buffer was sufficiently large. We use this to determine if we
     * need to repeat on a larger buffer to account for strings of any length */
    int len = vsnprintf(result, STRSIZE, fmt, args);
    /* Cleanup */
    va_end(args);
    /* If the entire string fits in the temp buffer, return a copy */
    if (len < STRSIZE)
        return strdup(temp);
    /* Otherwise, allocate enough memory and repeat */
    char* result = (char*)malloc(len   1); //  1 for the null terminator
    /* The variadic argument pack is consumed already, so recreate it */
    va_start(args, fmt);
    /* Use the variadic argument variant of sprintf
     * (we already have enough allocated memory now, so no need for snprintf) */
    vsprintf(result, fmt, args);
    /* Cleanup */
    va_end(args);
    return result;
}

When you're done, don't forget to free the returned pointer!

char* my_string = my_sprintf("My %s", "format");
...
free(my_string);

CodePudding user response:

You have a problem with:

'¡'

This is not a single char. It is a UTF-8 character that occupies two char. In hex, it is:

'\xC2\xA1'

Or, depending upon endianness:

'\xA1\xC2'

The %c format will not handle this well. You want to use double quotes so it is a string and use %s.


The simple way is to pass down the buffer:

Edit: Adjusted to add a buffer length.

char *
generate(char *buf,size_t siz)
{

    snprintf(buf,siz,"%s%s Mund%c!","¡", "Hola", 'o');
    buf[siz - 1] = 0;

    return buf;
}

Closer to what you want is to allocate the result:

char *
generate(void)
{
    char buf[1000];

    snprintf(buf,sizeof(buf),"%s%s Mund%c!","¡", "Hola", 'o');
    buf[sizeof(buf) - 1] = 0;

    char *format = strdup(buf);

    return format;
}

Note that c does not have automatic free/garbage collection, so for this second example, the caller will have to do (e.g):

char *formatted = generate();

// do stuff with formatted ...

// free the result when no longer needed ...
free(formatted);

CodePudding user response:

@guard3 approach with clean-up.

Try printing to a local buf and duplicate it.
Otherwise allocate to the reported size needed.
Check for errors along the way.

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#define MY_APRINTF_BUF_SZ 256
#define MY_APRINTF_TRIES 3 // 2 or more

char* my_aprintf(const char *fmt, ...) {
  va_list args;
  char buf_local[MY_APRINTF_BUF_SZ];
  char *buf = buf_local;
  size_t buf_size = sizeof buf_local;

  // Try a few times.
  // Often the 1st time with a local buffer will be good enough for common sized strings
  // Otherwise it "should" work on the 2nd time.
  // Some corner cases may require more tries.
  // Give up after a while.
  for (int i = 0; i < MY_APRINTF_TRIES; i  ) {
    va_start(args, fmt);  // TBD: Review if va_copy better/required
    int length_needed = vsnprintf(buf, buf_size, fmt, args);
    va_end(args);
    if (length_needed < 0) {
      if (buf != buf_local) {
        free(buf);
      }
      return NULL;
    }
    size_t size_needed = (unsigned) length_needed   1u;

    if (size_needed <= buf_size) {
      if (buf == buf_local) { // First time
        buf = strdup(buf);  // Code your own my_strdup if strdup not available.
      }
      return buf;
    }

    // Get more memory
    void *ptr = realloc(buf, size_needed);
    if (ptr == NULL) {
      free(buf);
      return NULL;
    }
    buf = ptr;
    buf_size = size_needed;
  }

  // Give up
  free(buf);
  return NULL;
}

Sample usage

int main() {
  char *s = my_aprintf("%d %s\n", 42, "Hello world!");
  if (s) {
    puts(s);
    free(s);
  }
}

If needed

#include <errno.h>
#include <stdlib.h>

// Use as needed
char *my_strdup(const char *buf) {
  size_t size = strlen(buf)   1u;
  char *s = malloc(size);
  if (s) {
    memcpy(s, buf, size);
  } else {
#ifdef ENOMEM
    errno = ENOMEM;
#endif
  }
  return s;
 }
  • Related