Reputation: 14251
As the title states, how do I sprintf an array given a unknown format string? I.e. how do I fill in my_sprintf
?
char* my_sprintf(const char *format, char **args){
char *result = malloc(MAX_SIZE_STRING*sizeof(char));
// ???
return result;
}
int main(int argc, char *argv[]){
printf("%s\n", my_sprintf("%s %s %s", argv));
return 0;
}
Upvotes: 0
Views: 1119
Reputation: 25918
Too much time on my hands tonight, so here's a simple - if not all that particularly efficient - way. I'm taking you at your word literally when you say "I only intended %s
with the modifiers and such", in particular this won't cope with %%
and such:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const static size_t initial_list_size = 20;
/*
* Returns a list of indices of `format` at which the substring
* "%s" is located. -1 is the sentinel value for end of list.
* Caller is responsible for freeing the returned pointer.
*/
int * find_specifiers(const char * format) {
size_t list_size = initial_list_size, top = 0;
/* Dynamically allocate list for locations of specifiers */
int * loc_list = malloc(list_size * sizeof *loc_list);
if ( loc_list == NULL ) {
fputs("Error allocating memory.", stderr);
exit(EXIT_FAILURE);
}
loc_list[top] = -1;
/* Find all occurrences */
const char * needle = format;
while ( (needle = strstr(needle, "%s")) != NULL ) {
/* Add index of found substring to list */
loc_list[top++] = needle - format;
/* Increase size of list if necessary */
if ( top >= list_size ) {
list_size *= 2;
loc_list = realloc(loc_list, list_size * sizeof *loc_list);
if ( loc_list == NULL ) {
fputs("Error allocating memory.", stderr);
exit(EXIT_FAILURE);
}
}
/* Set new sentinel value and skip past current specifier */
loc_list[top] = -1;
needle += 2;
}
return loc_list;
}
/*
* Returns a dynamically allocated string equivalent to `format`
* with each occurrence in `format` of "%s" replaced with successive
* strings in the array pointed to by `args`. Caller is responsible
* for freeing the returned pointer.
*/
char * my_sprintf(const char *format, char **args){
int * loc_list = find_specifiers(format);
size_t outsize = strlen(format) + 1;
/* Calculate required size of output string */
for ( size_t i = 0; loc_list[i] != -1; ++i ) {
outsize += strlen(args[i]) - 2;
}
/* Allocate output string with calloc() to avoid
* the need to manually null-terminate. */
char *result = calloc(1, outsize);
if ( result == NULL ) {
fputs("Error allocating memory.", stderr);
exit(EXIT_FAILURE);
}
/* Copy `format`, and replace specifiers with
* successive strings contained in `args` */
size_t n_out = 0, current_spec = 0, n_fmt = 0;
while ( format[n_fmt] ) {
/* Copy the next argument */
if ( loc_list[current_spec] != -1 &&
n_fmt == (size_t) loc_list[current_spec] ) {
size_t n_arg = 0;
while ( args[current_spec][n_arg] ) {
result[n_out++] = args[current_spec][n_arg++];
}
++current_spec;
n_fmt += 2;
}
else {
/* Copy the next character of `format` */
result[n_out++] = format[n_fmt++];
}
}
free(loc_list);
return result;
}
int main(void){
char * args[] = {"These", "are", "args"};
char * result1 = my_sprintf("Arg 1: %s, Arg 2: %s, Arg 3 %s", args);
char * result2 = my_sprintf("There are no args here.", NULL);
printf("%s\n", result1);
printf("%s\n", result2);
free(result1);
free(result2);
return 0;
}
which outputs:
paul@MacBook:~/Documents/src/scratch$ ./mysprint
Arg 1: These, Arg 2: are, Arg 3 args
There are no args here.
paul@MacBook:~/Documents/src/scratch$
Upvotes: 1
Reputation: 141618
(This answer takes into account OP's comment "I only intended %s with the modifiers and such")
Note that it is possible to build your own va_list
, however the details of this are implementation-specific and extremely non-portable, so I'm not going to go down that road.
This is arguably the simplest way to do it:
if ( argc == 1 ) snprintf(buf, sizeof buf, format, argv[0]);
else if ( argc == 2 ) snprintf(buf, sizeof buf, format, argv[0], argv[1]);
else if ( argc == 3 ) snprintf(buf, sizeof buf, format, argv[0], argv[1], argv[2]);
// ...etc, so far as you want
You work out argc
by looping through argv
until you find NULL
(argv[argc]
is guaranteed to be NULL
).
This code uses a fixed buffer size char buf[MAX_LENGTH];
, and then you can strdup
to create your return value or whatever. If you want to avoid the MAX_LENGTH
limitation then you have to call snprintf
twice for each case: once to find out the length required; then malloc
, then call it again to do the actual printing.
If you would prefer a less cut-and-paste'y version there is an outline of the algorithm you will need:
argc
format
. (If you are writing the code that generates format, you could just supply a list of tokens instead of a string)snprintf(buf + upto, space_remaining, token, argv[i++]);
While doing this, you'll need to keep track of how much of buf
you have used up (and realloc
it at each step, if you are going to use the double-snprintf
method). Also check you stop before reaching argc
.
Upvotes: 1