#c #c #variadic-functions
#c #c #переменные функции #c
Вопрос:
Я хочу добавить функциональность в sprintf (). В частности, я хочу иметь возможность передавать ему свои собственные типы данных POD, но я не уверен, как это сделать.
Предположительно, если вы создадите va_list, вы можете передать его vsprintf () и заставить его выполнить тяжелую работу за вас — но мне все равно нужно получить доступ к va_list и извлечь элементы самостоятельно, прежде чем передавать va_list в vsprintf ().
Например, предположим, что следующий код:
struct mypod {
int somedata;
}; // just for example, you know
// somewhere else in the code...
mypod mp;
mp.somedata = 5325;
my_sprintf(myChrPtr, "%z", mp);
С новым кодом %z, соответствующим моему новому типу данных.
Я понимаю, что могут передаваться только указатели и структуры POD, в этом нет ничего страшного. Однако мне интересно, что произойдет, если я доберусь до конца va_list (получив аргументы с помощью va_arg()), а затем передам их в vsprintf()?
Спасибо!
Комментарии:
1. Лично я, столкнувшись с подобной проблемой, вместо того, чтобы писать оболочку вокруг
*printf
, я просто создаю процедуру для вывода строки из структуры / объекта, а затем печатаю результирующую строку так, как вы бы печатали любую другую строку. Я думаю, что результирующему коду намного проще следовать.2. Обратите внимание, что строчные буквы используются для органов стандартов; заглавные буквы разрешены для расширений формата (но даже в этом случае это может быть неразумно). В частности,
z
является модификатором (указывающимsize_t
значение) в C99.
Ответ №1:
Учитывая, что это va_arg
должно вызываться в правильном порядке и с правильным типом для правильного поведения, я думаю, вам нужно использовать fmt
по частям, обрабатывая нужные вам части и передавая остальное vsprintf
:
void
mysprintf(char *dst, char *fmt, ...)
{
char *p = fmt;
va_list va;
va_start(va, fmt);
do {
char *q = strchr(p, '|');
if (q) {
/* really should take const char *fmt and not write on it like this... */
*q = '';
}
dst = vsprintf(dst, p, va); /* let vsprintf do a subset */
if (q) {
dst = sprintf(dst, "%d", va_arg(va, int)); /* consume 1 int */
}
p = q;
} while (p);
va_end(va);
}
strcpy(fmt, "%s|%s|%s"); /* mutable fmt */
mysprintf(buf, fmt, "hello", 7, "world", 8, "!");
В моем примере я просто сделал |
похожим %d
, но реальный пример с дополнительными %...
экранированиями будет намного сложнее.
Комментарии:
1. Это хороший способ сделать это, но, вероятно, было бы довольно большим проектом создать функцию, эквивалентную printf со всеми ее спецификаторами.
2. @Seth: Вы неправильно поняли: вы не выполняете повторную реализацию
vsprintf
, для этого и нужен вызов в середине. Более сложная часть передает нечто большее, чем|
->int
, поскольку вопрос был для%z
->class
, что требует больше работы, чемstrchr
3. @Бен Джексон: Итак, вы хотите сказать, что используется спецификатор формата, отличный от того, который использует sprintf? Это все равно не сработало бы очень хорошо, поскольку мне все еще нужно удалить все экземпляры моего собственного типа данных из va_list перед отправкой его в vsprintf. Смотрите мой комментарий к ответу Сета.
4. @FurryHead: Ваш взгляд на решение таков: «предварительная обработка fmt и va_list выполняет мою часть, а затем вызывает vsprintf». Я не думаю, что вы можете это сделать, но вы можете сделать то, что я описал: пусть vsprintf сделает все, вплоть до вашего аргумента первого класса, обработает это, повторит.
5. @Бен Джексон: Аааа, я понимаю. Я попытаюсь это реализовать и расскажу вам, насколько хорошо это работает. Спасибо!
Ответ №2:
Создайте два va_list
s и используйте только один, чтобы сделать то, что вам нужно, затем передайте другой в vsprintf
или что-то еще:
va_list l1 = va_start(final_arg);
va_list l2 = va_start(final_arg);
// do stuff with l1 and l2 will be unaffected
vsprintf(/*using l2*/);
Помните, что va_list на самом деле является всего лишь указателем на место в стеке (я думаю, это зависит от конкретной реализации, но именно так обстоит дело там, откуда я родом). va_arg
возвращает объект, на который указывает va_list
приведенное значение к указанному типу, и увеличивает указатель на sizeof(thetype)
. Итак, если вы получаете два указателя и сохраняете один, не изменяя его, вы можете просто передать его другой функции.
Надеюсь, я не неправильно понял вопрос.
Комментарии:
1. Во-первых, я хочу использовать vsprintf . 😉 Во-вторых, имело бы смысл удалить пользовательские % коды перед передачей их в vsprintf() — В-третьих, я все еще не знаю, как пропустить аргумент. Тем не менее, спасибо!
2.
vsprintf
было бы то же самое. И пропустить аргумент было бы немного сложнее, если бы это был я, я бы использовал класс bytebuffer, добавив в буфер все аргументы, которые я не хотел пропускать, и передал указатель на его начало, какva_list
ожидал бы vsprintf. Но я думаю, вам нужен более простой способ, а я его не знаю. Извините.3. @FurryHead Если только вы не хотели изменить стек напрямую (скопировать все после элемента, который вы хотите удалить, поверх него, аналогично удалению элемента из середины массива), который находился бы на краю 🙂
4. Ха-ха, я полагаю, что нет. Я намерен использовать %z для своих собственных элементов, но я имел в виду удалить из va_list мои собственные типы данных, в противном случае что-то вроде
my_sprintf(chrPtr, "Testing my %s at %z in the %s region", chrPtr, myData, chrPtr);
— если этот va_list передан в vsprintf(), он увидит второй% s, указывающий на myData, что нехорошо!5. @FurryHead Да, это то, что я имею в виду. Вы бы скопировали chrPtr поверх myData и просто удалили %z .