I'm trying to write a custom sprintf to format strings, with no need to pass a variable to write output to.
What I'm doing is traversing the given string with a for loop, finding % char, moving a char forward, switch-case that next char
Here
utils.c
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>
char *concat(char *p_format, ...) {
char *p_concat_str = calloc(1, sizeof(char));
va_list args;
va_start(args, p_format);
for (unsigned int i = 0; i < strlen(p_format); i) {
if (p_format[i] == '%') {
i ;
void *p_arg_str = va_arg(args, char *);
/* printf("%s\n", (char *)p_arg_str); */
p_concat_str = realloc(p_concat_str, (strlen(p_arg_str) 1));
switch (p_format[i]) {
case 's':
strcat(p_concat_str, (char *)p_arg_str);
/* printf("%s\n", (char *)p_arg_str); */
break;
}
}
p_concat_str = realloc(p_concat_str, i 2);
p_concat_str[i] = p_format[i];
p_concat_str[i 1] = '\0';
}
return p_concat_str;
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>
int main() {
char *p_world = "World";
/* Hello World */
char *p_str = concat("Hello %s", p_world);
printf("Formatted str: %s | its len %lu\n", p_str, strlen(p_str));
free(p_str);
return 0;
}
But I can't realloc p_concat_str length and can't append the argument returned by va_arg to p_concat_str with strcat
This is what I got
-- Configuring done
-- Generating done
-- Build files have been written to: /home/prxvvy/workspace/cutils/cmake-build-debug
[2/2] Linking C executable cutils
Formatted str: Hello Ws | its len 8
CodePudding user response:
you can use vsnprintf()
inside your concat()
function to detect length of output string. If you don't pass the destination buffer and its size, vsnprintf()
will only count the length of the output string and return it.
I think this way is relatively optimal, less malloc/realloc
will be required for the program and the code will be slightly better readable.
char *concat(char *p_format, ...)
{
va_list args, tmp;
va_start(args, p_format);
#ifdef va_copy
va_copy(tmp, args);
#else
memcpy(&tmp, &args, sizeof(va_list));
#endif
int length = vsnprintf(0, 0, p_format, tmp);
va_end(tmp);
if (length <= 0)
{
va_end(args);
return NULL
}
char *dst_buf = (char*)malloc(length 1);
if (dst_buf == NULL)
{
va_end(args);
return NULL;
}
int bytes = vsnprintf(dst_buf, length 1, p_format, args);
va_end(args);
if (bytes <= 0)
{
free(dst_buf);
return NULL;
}
dst_buf[bytes] = '\0';
return dst_buf;
}
P.S. The GNU extension of the C library contains the function vasprintf
which can be used to achieve the same result:
#define _GNU_SOURCE
#include <stdio.h>
char* concat(const char *p_format, ...)
{
va_list args;
char *dst_buf = NULL;
int length = 0;
va_start(args, p_format);
length = vasprintf(&dst_buf, p_format, args);
va_end(args);
if (length <= 0 && dst_buf)
{
free(dst_buf);
return NULL;
}
dst_buf[length] = '\0';
return dst_buf;
}
CodePudding user response:
This seems to work.
Use another pointer to append to the string.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *concat(char *p_format, ...) {
size_t len = strlen ( p_format);
size_t length = len;
char *p_concat_str = malloc ( length 1); // allocate for characters in format
char *cur = p_concat_str; // pointer for appending
char *p_arg_str = NULL;
va_list args;
va_start ( args, p_format);
for (size_t i = 0; i < len; i) {
*cur = p_format[i]; // assign character
*(cur 1) = 0; // zero terminate
if (p_format[i] == '%') {
*cur = 0; // zero terminate
i ; // advance past %
switch (p_format[i]) {
case 's':
p_arg_str = va_arg(args, char *); // get next arg
size_t format_len = strlen ( p_arg_str);
length = format_len; // accumulate length
p_concat_str = realloc ( p_concat_str, length 1);
strcpy ( cur, p_arg_str);
cur = format_len; // advance append pointer
break;
}
}
cur = *cur != 0; //advance if not terminating zero
}
va_end ( args);
return p_concat_str;
}
int main() {
char *p_world = "World";
char *p_str = concat("Hello %s %s", "there", p_world);
printf("Formatted str: %s | its len %lu\n", p_str, strlen(p_str));
free(p_str);
return 0;
}