unix C++:如何将fprintf结果作为std::string而不使用sprintf

ncgqoxb0  于 2022-11-04  发布在  Unix
关注(0)|答案(8)|浏览(147)

我正在使用一个用C实现的开源UNIX工具,我需要更改一些代码以使其按我所需的方式工作。我希望尽可能地做最小的更改,希望我的补丁能被上游接受。最好是用标准C实现的解决方案,并且不会创建更多的外部依赖项。
这里是我的问题。我有一个C++类--让我们称之为“A”--它当前使用fprintf()将其格式很重的数据结构打印到文件指针。在其print函数中,它还递归地调用几个成员类的相同定义的print函数(“B”是一个例子)。还有一个类C,它有一个成员std::string“foo”,需要将其设置为A示例的print()结果。可以将其视为A的to_str()成员函数。
在伪代码中:

class A {
public:
  ...

  void print(FILE* f);
  B b;

  ...  
};

...

void A::print(FILE *f)
{
  std::string s = "stuff";
  fprintf(f, "some %s", s);
  b.print(f);
}

class C {
  ...
  std::string foo;
  bool set_foo(std::str);
  ...
}

...

A a = new A();
C c = new C();

...

// wish i knew how to write A's to_str()
c.set_foo(a.to_str());

我应该提到的是,C相当稳定,但是A和B(以及A的其他依赖项)处于不断变化的状态,因此所需的代码更改越少越好。当前的print(FILE* F)接口也需要保留。我考虑了几种实现A::to_str()的方法,每种方法都有优缺点:

  • 将对fprintf()的调用更改为sprintf()
  • 我不需要重写任何格式字符串
  • print()可以被重新实现为:(f)this.to;
  • 但是我需要手动分配char[],合并大量的c字符串,最后将字符数组转换为std::string
  • 尝试在字符串流中捕获.print()的结果
  • 我必须将所有的格式字符串转换为〈〈输出格式。有数百个fprintf()要转换:-{
  • print()必须重写,因为据我所知,没有标准方法可以从UNIX文件句柄(通过this guy says it may be possible)创建输出流。
  • 使用Boost的字符串format library
  • 更多的外部依赖。讨厌。
  • Format的语法与printf()的语法差别很大,这很烦人:

如果您有任何问题,请使用以下命令:

  • 使用Qt的QString::asprintf()函数
  • 不同的外部相依性。

那么,我是否已经用尽了所有可能的选择?如果是,你认为哪一个是我最好的选择?如果不是,我忽略了什么?

  • 谢谢-谢谢
zc0qhyus

zc0qhyus1#

下面是我喜欢的一个习惯用法,它可以使功能与'sprintf'相同,但返回一个std::string,并且不会出现缓冲区溢出问题。这段代码是我正在编写的一个开源项目的一部分(BSD许可证),所以大家可以随意使用它。


# include <string>

# include <cstdarg>

# include <vector>

# include <string>

std::string
format (const char *fmt, ...)
{
    va_list ap;
    va_start (ap, fmt);
    std::string buf = vformat (fmt, ap);
    va_end (ap);
    return buf;
}

std::string
vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.
    size_t size = 1024;
    char buf[size];

    // Try to vsnprintf into our buffer.
    va_list apcopy;
    va_copy (apcopy, ap);
    int needed = vsnprintf (&buf[0], size, fmt, ap);
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the
    // buffer.  On Linux & OSX, it returns the length it would have needed.

    if (needed <= size && needed >= 0) {
        // It fit fine the first time, we're done.
        return std::string (&buf[0]);
    } else {
        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So do a malloc of the right size and try again.
        // This doesn't happen very often if we chose our initial size
        // well.
        std::vector <char> buf;
        size = needed;
        buf.resize (size);
        needed = vsnprintf (&buf[0], size, fmt, apcopy);
        return std::string (&buf[0]);
    }
}

编辑:当我写这段代码的时候,我并不知道这需要C99的一致性,而且Windows(以及旧的glibc)有不同的vsnprintf行为,在这个行为中,它返回-1表示失败,而不是一个确定的度量需要多少空间。这是我修改过的代码,大家可以看一下吗?如果你认为可以,我会再次编辑,使它成为列出的唯一代价:

std::string
Strutil::vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.  Be prepared to allocate dynamically if it doesn't fit.
    size_t size = 1024;
    char stackbuf[1024];
    std::vector<char> dynamicbuf;
    char *buf = &stackbuf[0];
    va_list ap_copy;

    while (1) {
        // Try to vsnprintf into our buffer.
        va_copy(ap_copy, ap);
        int needed = vsnprintf (buf, size, fmt, ap);
        va_end(ap_copy);

        // NB. C99 (which modern Linux and OS X follow) says vsnprintf
        // failure returns the length it would have needed.  But older
        // glibc and current Windows return -1 for failure, i.e., not
        // telling us how much was needed.

        if (needed <= (int)size && needed >= 0) {
            // It fit fine so we're done.
            return std::string (buf, (size_t) needed);
        }

        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So try again using a dynamic buffer.  This
        // doesn't happen very often if we chose our initial size well.
        size = (needed > 0) ? (needed+1) : (size*2);
        dynamicbuf.resize (size);
        buf = &dynamicbuf[0];
    }
}
w9apscun

w9apscun2#

我使用的是#3:boost字符串格式库-但我必须承认,我从来没有遇到过格式规范差异的问题。
对我来说,工作起来就像一种魅力--外部依赖性可能会更糟(一个非常稳定的库)
编辑:添加了一个如何使用boost::format代替printf的示例:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);

会是这样的boost::format库:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);

希望这有助于澄清boost::format的用法
我已经在4或5个应用程序中使用boost::format作为sprintf / printf的替换(将格式化字符串写入文件,或将自定义输出写入日志文件),并且从来没有遇到过格式差异的问题。可能会有一些(或多或少模糊的)格式说明符不同--但我从来没有遇到过问题。
相比之下,我有一些格式规范,我不能真正做的流(因为我记得很多)

wbgh16ku

wbgh16ku3#

您可以将std::string和iostream与格式化一起使用,例如setw()调用和iomanip中的其他调用

wrrgggsh

wrrgggsh4#

The {fmt} library提供fmt::sprintf函数,该函数执行printf兼容的格式化(包括根据POSIX specification的位置参数),并将结果返回为std::string

std::string s = fmt::sprintf("The answer is %d.", 42);

免责声明:我是这个库的作者。

gt0wga4j

gt0wga4j5#

以下可能是替代解决方案:

void A::printto(ostream outputstream) {
    char buffer[100];
    string s = "stuff";
    sprintf(buffer, "some %s", s);
    outputstream << buffer << endl;
    b.printto(outputstream);
}

B::printto相似),并定义

void A::print(FILE *f) {
    printto(ofstream(f));
}

string A::to_str() {
    ostringstream os;
    printto(os);
    return os.str();
}

当然,你应该使用snprintf而不是sprintf来避免缓冲区溢出,你也可以选择性地将风险更大的sprintfs改为〈〈格式,这样更安全,同时尽可能少地修改。

c9qzyr3d

c9qzyr3d6#

你应该试试Loki库的SafeFormat头文件(http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf),它类似于boost的字符串格式库,但是保留了printf(...)函数的语法。
我希望这对你有帮助!

jpfvwuh4

jpfvwuh47#

这是关于序列化的吗?还是关于打印的正确性?如果是前者,也可以考虑boost::序列化。这都是关于对象和子对象的“递归”序列化。

ki1q1bka

ki1q1bka8#

很晚才来参加派对,但我会这样解决这个问题。
1:使用pipe(2)打开管道。
2:使用fdopen(3)将来自管道的写入fd转换为FILE *
3:将FILE *传递给A::print()
4:使用read(2)从读取fd中提取数据的缓冲器负载,例如一次1K或更多。
5:将每个缓冲区加载的数据附加到目标std::string
6:根据需要重复步骤4和5以完成任务。

相关问题