Возобновление [vf]? Nprintf после достижения предела - PullRequest
0 голосов
/ 10 ноября 2009

У меня есть приложение, которое печатает строки в буфер, используя snprintf и vsnprintf. В настоящее время, если он обнаруживает переполнение, он добавляет> к концу строки как знак того, что строка была обрезана, и выводит предупреждение в stderr. Я пытаюсь найти способ возобновить строку [с того места, где она остановилась] в другом буфере.

Если бы он использовал strncpy, это было бы легко; Я знаю, сколько было записано байтов, и поэтому я могу начать следующую печать с * (p + bytes_written); Однако с printf у меня есть две проблемы; во-первых, спецификаторы форматирования могут занимать больше или меньше места в конечной строке, как и в строке формата, а во-вторых, мой valist может быть частично проанализирован.

У кого-нибудь есть простое решение?

РЕДАКТИРОВАТЬ: я, вероятно, должен уточнить, что я работаю над встроенной системой с ограниченным объемом памяти + без динамического выделения [т.е. я не хочу использовать динамическое распределение] Я могу печатать сообщения размером 255 байт, но не больше, хотя могу печатать столько, сколько хочу. Однако у меня нет памяти для выделения большого количества памяти в стеке, и моя функция печати должна быть поточно-ориентированной, поэтому я не могу выделить только один глобальный / статический массив.

Ответы [ 3 ]

2 голосов
/ 10 ноября 2009

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

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

Например, допустим, что в буфере осталось 3 байта, и средство форматирования начинает работать с преобразованием "% d" для значения -1234567. Он поместит «-1 \ 0» в буфер, а затем сделает все остальное, что нужно сделать, чтобы вернуть размер буфера, который вам действительно нужен.

В дополнение к тому, что вы можете определить, над каким спецификатором работал форматировщик, вам необходимо выяснить, что вместо ввода -1234567 во втором раунде вам нужно передать 234567. Я бросаю вам вызов, чтобы придумать разумный способ сделать это.

Теперь, если есть реальная причина, по которой вы не хотите перезапускать операцию сверху, вы, вероятно, могли бы обернуть вызов snprintf() / vsnprintf() чем-то, что разбивает строку формата, отправляя только один спецификатор преобразования за один раз и объединяя этот результат в выходной буфер. Вам нужно было бы придумать какой-нибудь способ, чтобы оболочка могла сохранять некоторое состояние при повторных попытках, чтобы она знала, какую спецификацию конвертации выбрать.

Так что, возможно, это в некотором смысле выполнимо, но, похоже, кажется, что было бы ужасно много работы, чтобы избежать гораздо более простой схемы «полного повтора». Я мог видеть, может быть (возможно), пробовать это в системе, где у вас нет роскоши динамически распределять больший буфер (встроенная система, может быть). В этом случае я бы, вероятно, утверждал, что нужен гораздо более простой / ограниченный форматер области видимости, который не обладает всей гибкостью форматеров printf() и может обрабатывать повторные попытки (поскольку их область действия более ограничена).

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

Edit:


На самом деле, я забираю часть этого обратно. Если вы хотите использовать настроенную версию snprintf() (назовем ее snprintf_ex()), я могу увидеть, что это относительно простая операция:

int snprintf_ex( char* s, size_t n, size_t skipChars, const char* fmt, ...);

snprintf_ex() (и его сопутствующие функции, такие как vsnprintf()) форматируют строку в предоставленный буфер (как обычно), но пропускают вывод первых skipChars символов.

Вероятно, вы могли бы довольно легко настроить это, используя исходный код из библиотеки вашего компилятора (или используя что-то вроде Holger Weiss 'snprintf()) в качестве отправной точки. Использование этого может выглядеть примерно так:

int bufSize = sizeof(buf);
char* fmt = "some complex format string...";

int needed = snprintf_ex( buf, bufSize, 0, fmt, arg1, arg2, etc, etc2);

if (needed >= bufSize) {
    // dang truncation...

    // do whatever you want with the truncated bits (send to a logger or whatever)

    // format the rest of the string, skipping the bits we already got
    needed = snprintf_ex( buf, bufSize, bufSize - 1, fmt, arg1, arg2, etc, etc2);

    // now the buffer contains the part that was truncated before. Note that 
    //  you'd still need to deal with the possibility that this is truncated yet
    //  again - that's an exercise for the reader, and it's probably trickier to
    //  deal with properly than it might sound...
}

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

2 голосов
/ 10 ноября 2009

Функции C99 snprintf() и vsnprintf() возвращают количество символов, необходимое для печати всей строки формата со всеми аргументами.

Если ваша реализация соответствует C99, вы можете создать массив, достаточно большой для ваших выходных строк, а затем обработать их при необходимости.

int chars_needed = snprintf(NULL, 0, fmt_string, v1, v2, v3, ...);
char *buf = malloc(chars_needed + 1);
if (buf) {
  snprintf(buf, chars_needed + 1, fmt_string, v1, v2, v3, ...);
  /* use buf */
  free(buf);
} else {
  /* no memory */
}
0 голосов
/ 11 апреля 2011

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

Сначала попробуйте распечатать строку в один буфер с помощью snprintf. Если он не переполняется, вы сэкономили много работы.

Если это не сработает, создайте новый поток и канал (с функцией pipe()), fdopen пишущий конец канала и используйте vfprintf для записи строки. Получите новый поток read с конца чтения канала и разбейте выходную строку на 255-байтовые сообщения. Закройте трубу и соедините ее с резьбой после возврата vfprintf.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...