Получить вывод vsnprintf () по частям

У меня есть массив символов размера N, и мне нужно получить вывод vsnprintf в разных частях, если его длина превышает размер массива символов минус 1 (N-1 байт).

Я хочу реализовать что-то вроде printf, но выводить его через UART. Я не хочу, чтобы N было больше 100. Если кто-то хочет напечатать строку длиной более 100 символов, я хочу делать это по частям. Я использовал vsnprintf, но не знаю, как получить его вывод по частям. Возможно, это неправильная функция из библиотеки stdio, я также взглянул на vsnprintf_s и _vscprintf, но все же я понятия не имею, как достичь того, что я хочу сделать. Я не хочу вызывать malloc и не хочу использовать VLA, потому что я хочу, чтобы максимальная длина буфера была 100, но в то же время чтобы можно было выводить по частям строку длиной более 100 байт.

char char_array[100];
void uart_print(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsnprintf(char_array, sizeof(char_array), fmt, args); /* Don't get the result because it is not useful for me */
    uart_output(char_array);
}

Фактический результат - это строка, выводимая через UART, обрезанная до 100 байтов. И мне нужен полный строковый вывод.

Я бы хотел сделать что-то вроде этого:

char char_array[100];
void uart_print(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int ret;
    unsigned int start_index = 0;
    size_t max_s = sizeof(char_array);
    do {
        ret = vsnprintf(char_array, max_s, start_index, fmt, args); /* The new parameter is number 3, it would specify from which point from the generated string it starts to save data in char_array */
        uart_output(char_array);
        start_index += max_s;
    } while (max_s <= ret);
}

person adriapm    schedule 11.07.2019    source источник
comment
Я не думаю, что это можно сделать с помощью стандартной библиотеки sprintf семейства функций, вам, вероятно, придется заново реализовать вариант с отслеживанием состояния.   -  person Konrad Rudolph    schedule 11.07.2019
comment
vsnprintf() в больший буфер, но отправьте результат в uart_output() более короткими частями.   -  person wildplasser    schedule 11.07.2019
comment
Вы можете вызвать vsnprintf без буфера, чтобы узнать, насколько он должен быть большим, или проверить asprintf. Вы также можете вызвать его с буфером в 102 байта, чтобы узнать, подходит ли он для одного сообщения, а затем иметь стратегию резервного копирования.   -  person Gem Taylor    schedule 11.07.2019
comment
Спасибо за ваши предложения, но, как я уже сказал, мне не нужны ни VLA, ни malloc, ни буфер размером более 100 байт.   -  person adriapm    schedule 11.07.2019
comment
Я хотел бы смесь strtok и vsnprintf.   -  person adriapm    schedule 11.07.2019
comment
но что ограничивает ваш буфер, доступное пространство стека или UART?   -  person wildplasser    schedule 11.07.2019
comment
Затем вам нужно реализовать свой собственный printf. По-другому никак. Вы можете пойти с _GNU_SOURCE, получить fopencookie и предоставить пользовательский cookie_write_function_t, но это все равно вызовет внутри довольно большой malloc. Но другого пути нет (к сожалению, мне очень хотелось бы, чтобы было cbprintf(int (*putc_cb)(void *cookie, char c), void *cookie, const char *fmt, ...).   -  person KamilCuk    schedule 11.07.2019
comment
@wildplasser Я ограничиваю буфер. Я делаю это, потому что хотел добиться этого, я внедряю программное обеспечение для встраиваемых систем и хотел избежать опасности.   -  person adriapm    schedule 11.07.2019
comment
Большое спасибо за ваши комментарии. Я бы хотел, чтобы было что-то вроде: vsnprintf(char *str, size_t n, size_t start_index, const char *format, va_list arg).   -  person adriapm    schedule 11.07.2019
comment
Я только что отредактировал вопрос, добавив в конце пример кода того, что я хотел бы сделать.   -  person adriapm    schedule 11.07.2019


Ответы (2)


Вы можете использовать fopencookie для внедрения вашей собственной функции вывода для вывода потока.

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


   #define CHUNK_SZ 2
   void uart_output(const char *buf, int size )
   {
       fwrite( buf,1,size,stdout );
       putchar(10);
   }

   ssize_t cf_write(void *cookie, const char *buf, size_t size)
   {
       while( size > CHUNK_SZ ) {
         uart_output(buf, CHUNK_SZ );
         size -= CHUNK_SZ;
         buf += CHUNK_SZ;
       }
       if( size ) uart_output(buf, size );
       return size;
   }

   static cookie_io_functions_t cf = { write: cf_write };

   void uart_print(const char *fmt, ...)
   {
     va_list args;
     va_start(args, fmt);
     FILE *fp = fopencookie(NULL, "w", cf );
     vfprintf( fp, fmt, args );
     fclose(fp);
     va_end(args);
   }

 int main(int argc, char **argv)
 {
   uart_print( "hello world %s", "test" );
 }
person Jens Harms    schedule 11.07.2019
comment
Спасибо за ответ. С одной стороны, где-то есть буфер длиннее, чем CHUNK_SZ, с вашим решением я просто не создаю этот буфер напрямую, а косвенно он создается. С другой стороны, компилятор, который я использую для микроконтроллера, не содержит библиотек для использования fopencookie, и я не собираюсь их добавлять. Но спасибо за подход, интересно. - person adriapm; 11.07.2019

Вы можете разделить строку формата (и форматирование) на более мелкие сегменты, каждый из которых содержит 1 (или меньше) % преобразований.

Ниже пример:

  • все еще не идеально
  • ни полный
  • обычно вы объединяете функцию parse_fmt() с форматированием цикла for в main(), теряя массив структур chunks.
  • даже если буфер составляет 100 символов, есть также fmtbuff[]; и функциям xxxprintf() также потребуется X * этого размера.

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>

        //dummie for testing
void uart_output(char *buff) { while(*buff) putc(*buff++,stdout); }

struct chunk    {
        unsigned start;
        unsigned end;   //next%
        char ll;
        char type;
        union   {
                char c;
                int i;
                long int li;
                long long int lli;
                unsigned u;
                unsigned long lu;
                unsigned long long llu;
                long double ld;
                double d;
                char *s;
                void *p;
                } u;
        } chunks[10];

        /* Chop the format string to pieces, with at most one '%' conversion per chunk.
        ** This is still not perfect,
        ** not all conversion types art present yet
        ** and backslash-escapes are not recognised.
        */
#define TINY_BUFFER_SIZE 100

int parse_fmt(struct chunk *arr, const char *fmt, ...) {
    unsigned idx,pos;
    int ch,state,ll;

    va_list args;
    va_start(args, fmt);

    arr[0].start=0;
    arr[0].type=0;
    state=0;
    ll=0;
    for(idx=pos=0; ch=fmt[pos]; pos++)  {
        // fprintf(stderr,"Idx=%d Pos=%u State=%d Char='%c'\n",idx,pos,state, ch);
        switch(state){
        case 0:
                if(pos-arr[idx].start>TINY_BUFFER_SIZE/2)goto next;
                if(ch =='%') {
                        if (!idx) goto next;
                        else {state++; continue; }
                        }
                continue;
        case 1:
                if(ch =='%'){state=0;continue;}
                if(pos-arr[idx].start>80)goto next;
                state++;
                // falltru
        case 2:
                switch(ch) {
                default: // ignore all modifiers except'l'
                case'h': continue;
                case'z': ll=2;continue;
                case'l': ll++;continue;
                case 'c': arr[idx].type= 'c';
                        arr[idx].u.c = va_arg(args,int);
                         break;
                case 'd': arr[idx].type= 'i';
                        if(ll ==2) arr[idx].u.lli = va_arg(args,long long int);
                        else if(ll ==1) arr[idx].u.li = va_arg(args,long int);
                        else arr[idx].u.i = va_arg(args,int);
                        break;
                case 'X':
                case 'x':
                case 'u': arr[idx].type= 'u';
                        if(ll ==2) arr[idx].u.llu = va_arg(args,long long unsigned int);
                        else if(ll ==1) arr[idx].u.lu = va_arg(args,long unsigned int);
                        else arr[idx].u.u = va_arg(args,unsigned int);
                         break;
                case 'g':
                case 'f': arr[idx].type= 'f';
                        if(ll) arr[idx].u.ld = va_arg(args,long double);
                        else arr[idx].u.d = va_arg(args,double);
                         break;
                case 's': arr[idx].type= 's';
                        arr[idx].u.s = va_arg(args,char*);
                         break;
                case 'p': arr[idx].type= 'p';
                        arr[idx].u.p = va_arg(args,void*);
                         break;
                }
                state++; continue;
        case 3:
                // falltru
        next:
                arr[idx].ll=ll;
                arr[idx].end=pos;
                if(idx++) state=0; else state=1;
                arr[idx].start=pos;
                arr[idx].type=0;
                ll=0;
                break;
                }
        }
if(state)  goto next;   // Haha!
if(idx) arr[idx-1].end=pos;
va_end(args);
return idx;
}

int main(void){
int idx,len,res;
char buff[TINY_BUFFER_SIZE];
char fmtcopy[TINY_BUFFER_SIZE];
char*fmt;

fmt = "Haha! Int=%03d Unsigned=%8u Hex=%08x Char=%c"
"... very long string here... very long string here... very long string here... very long string here... very long string here..."
" Float=%8.7f Double=%g %s OMG the end.\n";

len = parse_fmt(chunks, fmt
        , 666, (unsigned)42, (unsigned)0xaa55, '?' , 3.1415926535, sqrt(314.0),"done!"
        );

for(idx=0;idx<len;idx++)        {
        if(0) fprintf(stderr,"%d:{%u,%u}%d,%c"
        ,idx
        , chunks[idx].start
        , chunks[idx].end
        , chunks[idx].ll
        , chunks[idx].type
        );
        // select the part of the formatstring we are foing to handle
        res= chunks[idx].end-chunks[idx].start;
        memcpy(fmtcopy,fmt+chunks[idx].start, res);
        fmtcopy[res] = 0;
        if(0)fprintf(stderr,"\tfmtcopy='%s'", fmtcopy);

        switch( chunks[idx].type){
        case 0: // no percent sign present 
                res= snprintf(buff, sizeof buff, "%s", fmtcopy);
                break;
        case'p':
                res =snprintf(buff , sizeof buff, fmtcopy, chunks[idx].u.p);
                break;
        case's':
                res =snprintf(buff , sizeof buff, fmtcopy, chunks[idx].u.s);
                break;
        case'f':
                if(chunks[idx].ll>0) res =snprintf(buff, sizeof buff, fmtcopy, chunks[idx].u.ld);
                else res =snprintf(buff , sizeof buff, fmtcopy, chunks[idx].u.d);
                break;
        case'c':
                res =snprintf(buff , sizeof buff, fmtcopy, chunks[idx].u.i);
                break;
        case'i':
                if(chunks[idx].ll>1) res =snprintf(buff, sizeof buff, fmtcopy, chunks[idx].u.lli);
                else if(chunks[idx].ll>0) res =snprintf(buff, sizeof buff, fmtcopy, chunks[idx].u.li);
                else res =snprintf(buff , sizeof buff, fmtcopy, chunks[idx].u.i);
                break;
        case'x':
        case'u':
                if(chunks[idx].ll>1) res =snprintf(buff, sizeof buff, fmtcopy, chunks[idx].u.llu);
                else if(chunks[idx].ll>0) res =snprintf(buff, sizeof buff, fmtcopy, chunks[idx].u.lu);
                else res =snprintf(buff , sizeof buff, fmtcopy, chunks[idx].u.u);
                break;
                }
        if(0) fprintf(stderr,"\tRes =%d Buff='%s'\n",res ,buff);
        uart_output(buff);
        }
return 0;
}
person wildplasser    schedule 11.07.2019
comment
Спасибо за Ваш ответ. Я искал уже реализованную функцию в библиотеке stdio, для которой вы могли бы указать начальный индекс, с которого можно было бы начать копирование данных в вашу выходную переменную. Эта функция отбрасывает исходные данные до достижения start_index, а затем начинает копировать полученную строку непосредственно в мою выходную переменную. Я не думаю, что функциям stdio нужен какой-либо дополнительный буфер, я надеюсь и предполагаю, что они просто копируют вывод в указанную выходную переменную (или файловый дескриптор ...). Ваше предложение интересно, но я не хочу так сильно усложнять. - person adriapm; 12.07.2019
comment
Если stdio еще ничего не сделал, то, думаю, я буду использовать malloc () и free () в одной и той же процедуре. Я не буду использовать стековую память, чтобы избежать переполнения. - person adriapm; 12.07.2019
comment
В stdio (stdlib) такой функции нет. Если у вас есть память; используй это. Если функция print_xxx не требует повторного входа, вы можете использовать фиксированный статический буфер. Вызов malloc + free для каждого вызова print_xxx требует больших накладных расходов. (плюс: возможная фрагментация памяти) - person wildplasser; 12.07.2019