Передача одного списка аргументов двум вызовам v * printf () - PullRequest
1 голос
/ 22 августа 2011

Рассмотрим следующий тестовый пример:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdarg.h>

void test(char **outa, char **outb, const char* fstra, const char* fstrb, ...) {
    va_list ap;

    va_start(ap, fstrb);
    vasprintf(&outa, fstra, ap);
    vasprintf(&outb, fstrb, ap);
    va_end(ap);
}

int main(void) {
    char *a, *b;
    test(&a, &b, "%s", " %s\n", "foo", "bar");
    /* ... */
}

Намерение здесь состоит в том, что функция test() принимает две строки формата и список параметров для них обоих.Первая строка формата должна «съесть» столько аргументов, сколько ей нужно, а остальные должны использоваться для второй строки формата.

Таким образом, ожидаемый результат здесь будет foo & bar и вот что я получаю с glibc.Но AFAICS машина, работающая с кодовой панелью (думаю, что это * BSD), выдает foo & foo, и я предполагаю, что она использует va_copy() в списке аргументов.неопределенное (и уродливое) поведение здесь;поэтому возникает вопрос: есть ли способ получить строку двойного формата printf() без переопределения ее с нуля?И есть ли хороший способ проверить, что поведение с использованием autoconf без использования AC_RUN_IFELSE()?

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

Ответы [ 3 ]

4 голосов
/ 22 августа 2011

Когда вы вызываете одну из функций v*printf, она использует va_arg, что означает, что значение ap является неопределенным при возврате.

Соответствующий бит находится в секции 7.19.6.8 The vfprintf function в C99,который ссылается на сноску:

As the functions vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf, and vsscanf invoke the va_arg macro, the value of arg after the return is indeterminate.

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

Нет портативного способа сделать то, что вы пытаетесь, используя функции v*printf более высокого уровня, хотя вы могли бы прибегнуть к использованию более низкого уровняstuff.

Стандарт очень ясен в том смысле, что вызываемая функция, использующая va_arg в переменной va_list, делает ее неопределенной в вызывающей стороне.Начиная с C99 7.15 Variable Arguments <stdarg.h>:

The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

Однако значение ap при использовании va_arg в одной функции равно определитель (в противном случае обработка аргументов всей переменной будет нарушена).Таким образом, вы можете написать одну функцию, которая обрабатывает обе строки формата по очереди, с этими функциями более низкого уровня.

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

Я не уверен, насколько упрощением является приведенный вами пример кода, но, если он близок к реальностиВы можете получить тот же результат, просто предварительно скомбинировав две строки формата и используя его для передачи в vprintf, что-то вроде:

void test(const char* fstra, const char* fstrb, ...) {
    char big_honkin_buff[1024]; // Example, don't really do this.
    va_list ap;

    strcpy (big_honkin_buff, fstra);
    strcat (big_honkin_buff, fstrb);

    va_start(ap, big_honkin_buff);
    vprintf(big_honkin_buff, ap);
    va_end(ap);
}
1 голос
/ 22 августа 2011

Как уже сказано в другом ответе, передача ap в функцию v * () оставляет ap в неопределенном состоянии. Таким образом, решение не должно зависеть от этого состояния. Я предлагаю альтернативное решение.

Сначала инициализируйте ap как обычно. Затем определите длину первой отформатированной строки, используя vsnprintf(NULL, 0, fstra, ap). Объединить строки формата, повторно инициализировать ap и разбить вывод, используя предварительно определенную длину первой отформатированной строки.

Это должно выглядеть примерно так:

void test(const char* fstra, const char* fstrb, ...) {
  char *format, *buf;
  char *a, *b;
  int a_len, buf_len;
  va_list ap;

  va_start(ap, fstrb);
  a_len = vsnprintf(NULL, 0, fstra, ap);
  va_end(ap);

  asprintf(&format, "%s%s", fstra, fstrb);

  va_start(ap, fstrb);
  buf_len = vasprintf(&buf, format, ap);
  va_end(ap);
  free(format);

  a = malloc(a_len + 1);
  memcpy(a, buf, a_len);
  a[a_len] = '\0';

  b = malloc(buf_len - a_len + 1);
  memcpy(b, buf + a_len, buf_len - a_len);
  b[buf_len - a_len] = '\0';
  free(buf);
}

Как также обсуждалось в другом ответе , этот подход не разделяет позиционные заполнители printf -тиля ("%1$s. I repeat, %1$s."). Поэтому в документации по интерфейсу должно быть четко указано, что обе строки формата используют одно и то же пространство имен позиционных заполнителей, и что если в одной из строк формата используются позиционные заполнители, то обе должны.

0 голосов
/ 20 сентября 2012

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

В 32-битном Linux (и я тоже думаю, что в Windows) передача одного и того же ap двум функциям действительно работает.
Это потому, что va_list это просто указатель на место в стеке, где находятся параметры. v*rintf функции получают это, но не меняют его (они не могут, оно передается по значению).

В 64-битном Linux (не знаю о Windows) это не работает.
va_list является структурой, а v*printf получает указатель на нее (потому что на самом деле это массив структур размера 1). Когда аргументы используются, структура изменяется. Поэтому другой вызов v*printf получит параметры не с самого начала, а после последнего использованного.

Конечно, это не значит, что вы должны использовать va_list дважды в 32-битном Linux. Это неопределенное поведение, которое работает в некоторых реализациях. Не надейся на это.

...