C 有没有办法让snprintf在丢弃的字符处恢复?

C 有没有办法让snprintf在丢弃的字符处恢复?,c,printf,resume,C,Printf,Resume,出于超出本问题范围的原因,假设我有一组预先分配的不连续内存缓冲区。我希望能够将格式化字符串打印到容纳所有字符所需的尽可能多的缓冲区中。我不想动态分配一个足够大的缓冲区来容纳生成的字符串 有没有办法让snprintf在第一个丢弃的字符上“恢复”呢?或者是一种获得这种效果的方法,而不需要从stdlib重新实现整个格式解析代码 概念上: int nwritten = snprintf(message_buf[nextbuf++], MAX_MSG_LEN, fmt_string, var1, var2

出于超出本问题范围的原因,假设我有一组预先分配的不连续内存缓冲区。我希望能够将格式化字符串打印到容纳所有字符所需的尽可能多的缓冲区中。我不想动态分配一个足够大的缓冲区来容纳生成的字符串

有没有办法让snprintf在第一个丢弃的字符上“恢复”呢?或者是一种获得这种效果的方法,而不需要从stdlib重新实现整个格式解析代码

概念上:

int nwritten = snprintf(message_buf[nextbuf++], MAX_MSG_LEN, fmt_string, var1, var2);
while (nwritten > MAX_MSG_LEN) {
   already_written += MAX_MSG_LEN;
   //It didn't fit in one buffer, write any remaining characters to the next buffer.
   nwritten = snprintf_REMAINDER(message_buf[nextbuf++], already_written, MAX_MSG_LEN,
                                 fmt_string, var1, var2);
}
有了这些代码,这些输入

 MAX_MSG_LEN = 16;
 fmt_string = "%s: %.2f";
 char* var1 = "percent complete";
 float var2 = 45.6;
 char msgbuf[N][MAX_MSG_LEN];
 nextbuf = already_written = 0;
应导致
msgbuf[0]
包含
“完成百分比”

msgbuf[1]
包含
“e:45.06”

如何实现
snprintf\u余数

缓冲区是连续的。我们可以(ab-)使用它,只需写入它,然后设置零字节

将整个长度的trating
msgbuf
作为一个大数组写入缓冲区。然后,如果len大于16个字节,则每16个字节从第15个字节开始向右移动
nwrited-16+1
字节。在第15个字节处写一个
'\0'
,以结束字符串。并为
nwrited
中写入的每个块继续

以下是概念验证计划:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>

#define N 10
#define MAX_MSG_LEN 16
char msgbuf[N][MAX_MSG_LEN];
size_t nextbuf;

#define MIN(a, b)  ((a)<(b)?(a):(b))

void msg_output(void) {
    for (size_t i = 0; i < nextbuf; ++i) {
        printf("msg[%zu] = (%zu) '%s`\n", i, strlen(msgbuf[i]), msgbuf[i]);
    }
}

#ifdef __GNUC__
__attribute__((__format__(__printf__, 1, 2)))
#endif
void msg_printf(const char *fmt, ...) {
    const size_t rows = sizeof(msgbuf)/sizeof(*msgbuf);
    assert(nextbuf <= rows);
    if (nextbuf >= rows) return;

    // treat msgbuf as continous space
    char *buf = (char*)msgbuf + nextbuf * MAX_MSG_LEN;
    size_t bufsize = sizeof(msgbuf) - nextbuf * MAX_MSG_LEN;

    va_list va;
    va_start(va, fmt);
    int len1 = vsnprintf(buf, bufsize, fmt, va);
    va_end(va);
    assert(len1 >= 0);
    size_t len = len1;

    // insert zero bytes at specific positions
    const size_t chunk = sizeof(*msgbuf);
    while (len >= chunk) {
        const size_t want_to_move = len - chunk + 1;
        const size_t move = MIN(want_to_move, bufsize - chunk);
        memmove(&buf[chunk], &buf[chunk - 1], move);
        buf[chunk - 1] = '\0';

        buf += chunk;
        bufsize -= chunk;
        len -= chunk;
    }
    buf += len + 1;
    buf[0] = '\0';

    // increment out buffer position
    nextbuf = (buf - (char*)msgbuf + chunk - 1) / chunk;
}

int main() {
    msg_printf("hello world");
    msg_printf("hello 123456789 123456789");
    msg_printf("hello 123456789 123456789 123456789 123456789");
    msg_output();
}
这段代码有很多错误,只是为了证明概念。最重要的是,当到达缓冲区的末尾时,它会导致缓冲区溢出,在
memmove
之前的检查不起作用。最后一个
9
也丢失

我还怀疑,一个非常积极的优化编译器可以防止未定义的行为(
\u FORTIFY\u SOURCE
)可能会在
vsnprintf
调用上触发一些奇怪的行为,因为编译器可能认为
\u内置对象大小(buf)
等于
MAX\u MSG\u LEN
,低于计算的
bufsize
。无论是哪种情况,我都希望使全局变量只是一个连续数组,如
char-msgbuf\u buf[N*MAX\u MSG\u LEN]
,并编写一个访问器来访问单独的行,如
char*msgbuf(size\u t row){return&msgbuf\u buf[row*MAX\u MSG\u LEN]}

至于:

有没有办法让snprintf在丢弃的字符处恢复


不,没有这种方法。

没有这种方法,因为数据和格式字符串之间没有相关性。它甚至可以在一个参数中间停止。要么全力以赴,要么一事无成。如果您知道格式字符串,那么您可能可以检查已经写入的字符串,但我怀疑这是否值得费心。
编写任何剩余字符
我不明白,是否总是零剩余字符?如果它不适合缓冲区,缓冲区将满,还有什么要写的<代码>概念上:您能提供更真实的代码,并提供一个完整的函数,该函数接受您期望的输入和一些测试输出吗
snprintf(message_buf[nextbuf++]
是一个指针数组吗?我认为缓冲区通常是一个字符数组。您使用的是什么环境,是POSIX还是GNU?您使用标准流还是文件流?如果不是,很多(嵌入式)工具链允许您覆盖正常的流输出函数。您可以定义自己的文件句柄打开、写入和关闭函数,这些函数将执行必要的缓冲,然后使用标准的
fprintf
格式化字符串。是否可以选择?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>

#define N 10
#define MAX_MSG_LEN 16
char msgbuf[N][MAX_MSG_LEN];
size_t nextbuf;

#define MIN(a, b)  ((a)<(b)?(a):(b))

void msg_output(void) {
    for (size_t i = 0; i < nextbuf; ++i) {
        printf("msg[%zu] = (%zu) '%s`\n", i, strlen(msgbuf[i]), msgbuf[i]);
    }
}

#ifdef __GNUC__
__attribute__((__format__(__printf__, 1, 2)))
#endif
void msg_printf(const char *fmt, ...) {
    const size_t rows = sizeof(msgbuf)/sizeof(*msgbuf);
    assert(nextbuf <= rows);
    if (nextbuf >= rows) return;

    // treat msgbuf as continous space
    char *buf = (char*)msgbuf + nextbuf * MAX_MSG_LEN;
    size_t bufsize = sizeof(msgbuf) - nextbuf * MAX_MSG_LEN;

    va_list va;
    va_start(va, fmt);
    int len1 = vsnprintf(buf, bufsize, fmt, va);
    va_end(va);
    assert(len1 >= 0);
    size_t len = len1;

    // insert zero bytes at specific positions
    const size_t chunk = sizeof(*msgbuf);
    while (len >= chunk) {
        const size_t want_to_move = len - chunk + 1;
        const size_t move = MIN(want_to_move, bufsize - chunk);
        memmove(&buf[chunk], &buf[chunk - 1], move);
        buf[chunk - 1] = '\0';

        buf += chunk;
        bufsize -= chunk;
        len -= chunk;
    }
    buf += len + 1;
    buf[0] = '\0';

    // increment out buffer position
    nextbuf = (buf - (char*)msgbuf + chunk - 1) / chunk;
}

int main() {
    msg_printf("hello world");
    msg_printf("hello 123456789 123456789");
    msg_printf("hello 123456789 123456789 123456789 123456789");
    msg_output();
}
msg[0] = (11) 'hello world`
msg[1] = (15) 'hello 123456789`
msg[2] = (10) ' 123456789`
msg[3] = (15) 'hello 123456789`
msg[4] = (15) ' 123456789 1234`
msg[5] = (14) '56789 12345678`