Функция обтекания sscanf для продвижения указателя строки в C - PullRequest
7 голосов
/ 17 марта 2010

У меня есть функция, которая выполняет серию вызовов на sscanf(), а затем, после каждого, обновляет указатель строки, чтобы он указывал на первый символ, не использованный sscanf(), например:

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

Чтобы очистить его и избежать дублирования несколько раз, я хотел бы заключить его в красивую служебную функцию, которая выглядит примерно так:

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;
}

Тогда я мог бы упростить вызовы, как показано ниже, чтобы удалить дополнительный параметр / бухгалтерию:

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

К сожалению, я не могу найти способ добавить параметр &length в конец списка аргументов напрямую или иным образом внутри newSscanf(). Есть ли способ обойти это, или я так же хорошо справляюсь с ведением бухгалтерии вручную при каждом вызове?

Ответы [ 3 ]

3 голосов
/ 17 марта 2010

Вы правы - вы не можете вставить дополнительные параметры в va_list. Лучшее, что вы можете сделать, - это, вероятно, какой-нибудь макрос-трюк, подобный этому:

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)

... и требуется, чтобы вызывающий абонент сделал:

NEW_SSCANF_INIT;

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

Если вы можете использовать расширения GCC, вы можете использовать «выражения выражений», чтобы покончить с частью NEW_SSCANF_INIT, делая ее чище:

#define newSscanf(str, fmt, ...) ({int _ss_len; _newSscanf(str, &_ss_len, fmt "%n", __VA_ARGS__, &_ss_len);})
1 голос
/ 17 марта 2010

Если не считать выяснения того, как списки переменных работают под прикрытием (и, следовательно, сделать ваш код непереносимым), изменить аргументы невозможно.

Но у меня была одна мысль, которая может или не может работать. Я не проверял это, так как я действительно не думаю, что вы должны использовать это, но, если вы одержимы этим, это может помочь.

Поскольку вы просто хотите получить количество отсканированных символов, вы должны понимать, что вам не нужно делать это одновременно с фактической настройкой переменных вызывающей стороны.

Пусть ваш код отсканирует строку, чтобы установить аргументы в соответствии с требованиями вызывающей стороны. Там вообще не нужно никаких изменений.

Следующий этап - немного хитрый.

Подсчитайте количество % символов в строке формата, за которыми сразу же не следует % или * - другими словами, количество переменных, которые необходимо указать для sscanf. Подтвердите, превышает ли это ваш верхний предел (см. Код ниже).

Затем добавьте последовательности %n в конец строки формата, чтобы получить количество символов.

Затем, используя новую строку формата, используйте ненужный буфер (несколько раз), чтобы получить все значения из сканирования, включая последнее (количество символов).

Примерно так (ответственность за отладку лежит на вас):

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;
}

Мне было бы интересно услышать, как все пойдет, если вы решите попробовать. Это моя работа (и ответственность) сделано. Я рад продать вам бензопилу, но, если вы отрежете ногу во время использования, это ваша проблема: -)

0 голосов
/ 17 марта 2010

Вы вызываете функцию неправильно, посмотрите на параметр для char **str, который подразумевает параметр вызова по ссылке:

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