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:
- string length is unknown
- 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;
}