0

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; } 
8
  • Are you asking about how to make your own sprintf from scratch or just to pass a variable list of arguments to the appropriate functions? This existing thread covers the wrapping: stackoverflow.com/questions/1056411/… Commented May 4, 2014 at 2:59
  • 1
    This is actually not possible without parsing format yourself, which is a similar amount of work to just fully implementing sprintf yourself, or doctoring a freeware implementation of sprintf. However, if the calling function can pass va_list args instead of char **args, then you can use vsprintf (or preferably, vsnprintf). Commented May 4, 2014 at 3:05
  • It's not clear if you want to be able to handle %d, %f, %-20.15s, etc. (and if so, how you are going to pass the args in). If your function only expects to handle %s exactly and nothing else, then this is possible. Commented May 4, 2014 at 3:07
  • @MattMcNabb no, I understand that the others arent possible. I only intended %s with the modifiers and such. Commented May 4, 2014 at 3:09
  • @JamesSnyder its not about a "variable list of arguments" its about an array, specifically (unless if there is a way to convert an array to a va_list) Commented May 4, 2014 at 3:10

2 Answers 2

1

(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:

  1. Find argc
  2. Tokenize format. (If you are writing the code that generates format, you could just supply a list of tokens instead of a string)
  3. For each token, Call 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.

Sign up to request clarification or add additional context in comments.

Comments

1

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$ 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.