views:

297

answers:

3

I have a function that makes a series of calls to sscanf() and then, after each, updates the string pointer to point to the first character not consumed by sscanf() like so:

if(sscanf(str, "%d%n", &fooInt, &length) != 1)
{ 
   // error handling
}
str+=length;

In order to clean it up and avoid duplicating this several times over, i'd like to encapsulate this into a nice utility function that looks something like the following:

int newSscanf ( char ** str, const char * format, ...)
{
  int rv;
  int length;
  char buf[MAX_LENGTH];
  va_list args;

  strcpy(buf, format);
  strcat(buf, "%n");
  va_start(args, format);
  rv = vsscanf(*str, buf, args, &length);  //Not valid but this is the spirit
  va_end(args);
  *str += length;

  return rv;
}

Then I could simplify the calls as below to remove the additional parameter/bookkeeping:

if(newSscanf(&str, "%d", &fooInt) != 1)
{ 
   // error handling
}

Unfortunately, I can't find a way to append the &length parameter onto the end of the arg list directly or otherwise inside newSscanf(). Is there some way to work around this, or am I just as well off handling the bookkeeping by hand at each call?

+1  A: 

Short of figuring out how variadic lists work under the covers (and hence rendering your code non-portable), there's no way to modify the arguments.

But I did have one thought which may or may not work. I haven't tested it since I really don't think you should use it but, if you're hell-bent on doing this, it may help.

Since you just want to get the number of characters scanned, you should realise that you don't have to do it at the same time as the actual setting of the caller's variables.

Have your code scan the string to set the arguments as needed by the caller. No change needed there at all.

The next stage is the slightly tricky one.

Count up the number of % characters in the format string that are not immediately followed by % or * - in other words, the number of variables that need to be supplied to the sscanf. Assert if this is greater than your upper limit (see code below).

Then add a %n sequences to the end of your format string to ensure that you will get the character count.

Then, using your new format string, use a junk buffer (repeatedly) to receive all values from the scan including the last (character count).

Something like this (the responsibility for debugging is yours):

typedef union {
    char junk[512]; // Be *very* careful with "%s" buffer overflows.
    int length;
} tJunkbuff;

int newSscanf (char **str, const char *format, ...) {
    int rv, length;
    char buf[MAX_LENGTH];
    tJunkBuff junkbuff;
    va_list args;

    // Populate variables.

    va_start (args, format);
    rv = vsscanf (*str, buf, args);
    va_end (args);

    // Get length.

    // String scanning for % count and assert/error left out.
    // Only 20 allowed (depends on number of jb.junk variables below (n-1)).
    strcpy (buf, format);
    strcat (buf, "%n");
    sscanf (*str, buf,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk,jb.junk, jb.junk, jb.junk, jb.junk,
        jb.junk); // May need to be "&(jb.junk)" ?
    *str += jb.length;

    return rv;
}

I'd be interested in hearing how it goes if you decide to give it a shot. That's my work (and responsibility) done. I'm happy to sell you the chainsaw but, if you cut your leg off while using it, that's your problem :-)

paxdiablo
Yeah, I think that's a bit too much of a hack to use in a production system, but +1 for a creativeness and abusing unions =D
Dusty
It would need to be an actual `char *` in the union, pointing at the `char[512]` buffer. It doesn't work portably anyway - imagine 16 bit `int` and 32 bit `char *` - then you can get desynchronised so it writes the value for `"%n"` into the padding in the union.
caf
A: 
tommieb75
Yeah, I had a typo there which is now fixed, thanks. Unfortunately, the problem still stands that I can't add something to the va_list
Dusty
+2  A: 

You're right - you can't stuff extra parameters into a va_list. The best you can do is probably some macro trickery like this:

int _newSscanf ( char ** str, int *length, const char * format, ...)
{
  int rv;
  va_list args;

  va_start(args, format);
  rv = vsscanf(*str, format, args);
  va_end(args);
  *str += *length;

  return rv;
}

#define NEW_SSCANF_INIT int _ss_len
#define newSscanf(str, fmt, ...) _newSscanf(str, &_ss_len, fmt "%n", __VA_ARGS__, &_ss_len)

...and require the caller to do:

NEW_SSCANF_INIT;

if (newSscanf(&str, "%d", &fooInt) != 1)
{ 
   // error handling
}

If you can use GCC extensions, you can use "statement expressions" to do away with the NEW_SSCANF_INIT part, making it cleaner:

#define newSscanf(str, fmt, ...) ({int _ss_len; _newSscanf(str, &_ss_len, fmt "%n", __VA_ARGS__, &_ss_len);})
caf
Other than replacing `buf` with `format` in _newSscanf it works nicely as written, thanks!
Dusty
Fixed for posterity (and I've added an improvement if GCC extensions are available to you).
caf