【问题标题】:Return a const char* from a custom printf function从自定义 printf 函数返回一个 const char*
【发布时间】:2013-11-25 20:02:40
【问题描述】:

我编写了一个自定义打印函数。我的问题是我需要返回一个const char*,因为它必须在另一个函数中使用。我根本不知道如何管理...

anotherFunction (const char* text /*Here*/, unsigned __int32 value, unsigned __int64 bigVal);

我知道以下示例无法正常工作。这就是我到目前为止所尝试的。

const char* CatchMessage (const char *message, ...)
{
    va_list  args;
    va_start (args, message);
    /*?*/
    va_end   (args);
    return message;
}

我还只是设法在 cmd 中获得正确的输出,但我实际上需要它作为返回值。

void CatchMessage (const char *message, ...)
{
    va_list  args;
    va_start (args, message);
    vfprintf (stdout, message, args);
    va_end   (args);
}

呼叫:

CatchMessage ("Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);

应该返回:

"Some Input stuff and 12 equals to 6"

我无法找到解决方案。任何帮助将不胜感激。

问:如何让这个CatchMessage 函数返回格式正确的const char*

【问题讨论】:

  • 我建议检查您的系统是否有vasprintf()vs[n]printf()(后者可以调用两次——一次使用NULL 缓冲区来获取输出长度,然后再次在缓冲区上调用malloc /new)... 您的客户端代码需要负责释放内存。但是,有什么意义呢?你考虑过boost::formatstd::string吗?

标签: c++ c printf variadic-functions


【解决方案1】:

听起来CatchMessage 应该将指针指向一个字符缓冲区(及其大小),然后将vsnprintf() 指向该缓冲区。

【讨论】:

    【解决方案2】:

    既然您使用的是 C++(至少根据问题上的标签),为什么不直接返回 std::string 中的字符串?

    【讨论】:

    • 它也被标记为 C++
    • @TomTanner:上次听说有人推荐在 C++ 中使用 RAII;这里真正的问题是,最佳答案非常取决于您是否需要 C 或 C++ 解决方案。当然,您可以在 C++ 中使用 C 解决方案;但是为什么要标记它 C++ 呢?它什么也没带来。就我而言,除非 OP 有特定要求,否则任何标记为 C++ 的问题都将用 C++11 的全部优势(以及最佳实践)来回答。
    • 我原以为用 C 标记问题的人想要 C 解决方案。如果他们同时标记它,我怀疑他们是在混合环境中使用它。也许它可能是使用 C++ 方法的代码,但接口必须与 C 匹配。在这种情况下,在中间代码中使用 std::string 将是有效的。但是他说他需要一个 const char * 并且很可能他不能改变这个要求。告诉他返回一个 std::string 并没有多大帮助。
    • 他需要使用char const* 调用anotherFunction,但std::string 提供了这一点(使用c_str);不清楚的是anotherFunction 是否拥有所有权。如果不是:anotherFunction(format(...).c_str(), ...); 完美匹配,C++ 中的临时生命周期规则保证std::string 将在anotherFunction 调用结束后立即被销毁,这可能是完美的。老实说,这个问题确实没有明确说明:(
    • 我将其标记为 C 和 C++,因为我对这两种解决方案都持开放态度。获得它们之间的差异并不是一个大问题。我知道 std::string 在正常情况下可以工作,但在这种情况下我不能使用它。 :( 还是谢谢你。+1
    【解决方案3】:

    返回 (const) char * 的问题是你必须在某处有一个缓冲区。

    有几种方法可以做到这一点:

    1. 调用者必须提供该缓冲区
    2. 你必须malloc()
    3. 函数本身作为static 缓冲区,但这会使其不可重入 - 这对多线程等不利。

    广告 1:

    void CatchMessage(char * result, size_t maxlen, const char *message, ...)
    {
        va_list ap;
        va_start(ap, message);
        vsnprintf(result, maxlen, message, ap);
        va_end(ap);
    }
    

    调用

    char buffer[500];
    CatchMessage(buffer, sizeof buffer, "Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
    anotherfunction(buffer, ...)
    

    广告 2:

    char * CatchMessage(const char *message, ...)
    {
        size_t size = 500;
        char * result = malloc(size);
        if (!result) return NULL; // error handling!
        while (1) {
            va_list ap;
            va_start(ap, message);
            size_t used = vsnprintf(result, size, message, ap);
            va_end(ap);
            char * newptr = realloc(result, size);
            if (!newptr) { // error
                free(result);
                return NULL;
            }
            result = newptr;
            if (used <= size) break;
            size = used;
        }
        return result;
    }
    

    调用

    char * buffer = CatchMessage(buffer, sizeof buffer, "Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
    if (!buffer) { /* error handling: no memory! */ }
    anotherfunction(buffer, ...)
    free(buffer); // important for avoiding memory leaks
    

    广告 3:

    char * CatchMessage(const char *message, ...)
    {
    
        static char result[500]; // static is important here! Otherwise the memory will be freed immediately after returning.
        va_list ap;
        va_start(ap, message);
        vsnprintf(result, sizeof result, message, ap);
        va_end(ap);
        return result;
    }
    

    调用

    char * buffer = CatchMessage(buffer, sizeof buffer, "Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
    anotherfunction(buffer, ...)
    

    没有其他选择,尤其是不定义

    char result[500];
    

    在函数中然后返回它:这个数组存在于堆栈中,返回后立即释放。调用者不能安全地访问它;它的内容只是未定义。

    【讨论】:

      【解决方案4】:

      如果你不关心重入,你可以返回指向静态缓冲区的指针:

      #define MESSAGE_MAX 1024
      
      const char *
      CatchMessage (const char *message, ...)
      {
          static buffer[MESSAGE_MAX];
          va_list  args;
          va_start (args, message);
          vsnprintf (buffer, MESSAGE_MAX, message, args);
          va_end   (args);
          return buffer;
      }
      

      注意事项:

      1. 此实现不是线程安全的。如果您关心线程安全,请使用线程本地存储而不是静态缓冲区

      2. 此实现对消息长度有硬编码上限。如果这是不可取的,并且您的编译器符合 C99,您可以首先调用 vsprintf 并使用 NULL 作为第一个参数来了解结果字符串的长度,然后分配该侧的缓冲区。

      【讨论】:

        【解决方案5】:

        在 C++ 中,使用可变参数是不好的,因为它特别强调类型安全(和内存安全)。因此,您可能想提出一个实际上是类型安全的版本(是的,这是可能的),但又提出了一个类似的接口:

        template <typename H, typename... Args>
        void format(std::ostream& out,
                    char const* format,
                    size_t len,
                    H const& head,
                    Args const&... args);
        
        // Variations with 'char const (&)[N]' and 'std::string' formats
        // as well as variations returning directly a 'std::string'.
        

        现在,format 的实现已经不是太难了;特别是在不支持位置参数的情况下。一个简单的可以在下面找到:

        inline void format_string(std::ostream& out, char const* const* c) { out << *c; }
        inline void format_string(std::ostream& out, std::string const* s) { out << *s; }
        inline void format_string(std::ostream& out, void const*); // will throw
        
        template <typename Integral, typename = enable_integral<Integral>::type>
        inline void format_integral(std::ostream& out, Integral const* i) { out << *i; }
        inline void format_integral(std::ostream& out, void const*); // will throw
        
        inline size_t format_consume(std::ostream& out,
                                     char const* const format,
                                     size_t const length)
        {
            char const* end = format + length;
            char const* current = format;
        
            do {
                // 1. Find first "format identifier", output stuff in-between
                char const* perc = std::find(current, end, '%');
        
                if (perc != current) { out.write(current, perc - current); }
        
                current = perc;
        
                // 2. %% is % escaped by %, so output it directly
                while (*current == '%' and *(current + 1) == '%') {
                    out.put('%');
                    current += 2;
                }
            } while (current != end and *current != '%');
        
            // 3. Return number of characters of format parameter consumed
            return current - format;
        } // format_consume
        
        inline void format(std::ostream& out, char const* format, size_t len) {
             size_t const consumed = format_consume(out, format, len);
        
             if (consumed != len) { throw std::runtime_exception("Missing arguments"); }
        } // format
        
        template <typename H, typename typename... Args>
        void format(std::ostream& out,
                    char const* format,
                    size_t len,
                    H const& head,
                    Args const&... args)
        {
             size_t const consumed = format_consume(out, format, len);
        
             if (consumed == len) { throw std::runtime_exception("Extraneous arguments"); }
        
             format += consumed;
             len -= consumed;
        
             assert(*format == '%');
        
             switch(*(format+1)) {
             case 's': format_string(out, &head); break;
             case 'd': format_integral(out, &head); break;
             default: throw std::runtime_exception("Invalid specifier");
             }
        
             format(out, format+2, len-2, args...);
        } // format
        

        在一般情况下,它稍微有点麻烦,因为您需要解析修饰符等...但是,对于生产就绪的实现,我建议查看 Boost.Format。

        【讨论】:

          【解决方案6】:
          char* CatchMessage (size_t size, const char *message, ...)
          {
              char result[size];
              va_list  args;
              va_start (args, message);
              vsprintf (result, message, args);
              va_end   (args);
              return result;
          }
          

          参考this

          对于glglgl:如果你想使用干净的方式,请使用vsnprintf(result, size, message, args)

          【讨论】:

          • 当然不是。 result 指向哪里?为什么是vsprintf() 而不是vsnprintf()
          • 抱歉,我的错 :) 忘记将 void 更改为 char*。为什么选择 vsprintf?因为如果他阅读我链接的文档,他会自己找到安全的方法;)
          • 如果生成的字符串超过 500 个字符怎么办?
          • 不,你不能——那么为时已晚,代码已编译。使用vsnprintf()有什么问题?然后所有的问题都没有了。
          • 不,它们不是:您的阵列如何记录日志?
          猜你喜欢
          • 2012-05-26
          • 1970-01-01
          • 2012-02-18
          • 2020-09-29
          • 1970-01-01
          • 2016-03-22
          • 2016-06-10
          • 2015-04-27
          相关资源
          最近更新 更多