【问题标题】:Inconsistent string/wchar content depending on code's location?不一致的字符串/wchar 内容取决于代码的位置?
【发布时间】:2019-01-04 17:39:22
【问题描述】:

我有 3 种不同的结果,具体取决于我使用的免费功能:

struct __declspec(dllexport) TimerPair final
{
    long long Time{};
    string Descr;
};

template<typename... T>
wchar_t* Message(T &&... args)
{
    wchar_t message[100];
    swprintf(message, 100, forward<T>(args)...);
    return message;
}

template<typename... T>
void LogMessage(T &&... args)
{
    Logger::WriteMessage(Message(forward<T>(args)...));
}

const wchar_t* ToWchar(string arg)
{
    std::wstring widestr = std::wstring(arg.begin(), arg.end());
    return widestr.c_str();
}

以及单元测试中的代码:(o 是一个TimerPair 结构)

// v1
LogMessage(L"%s : %.4fms\n", ToWchar(o.Descr), (float)o.Time / 1000000);

// v2
std::wstring widestr = std::wstring(o.Descr.begin(), o.Descr.end());
Logger::WriteMessage(Message(L"%s : %.4fms\n", widestr.c_str(), (float)o.Time / 1000000));

// v3
std::wstring widestr = std::wstring(o.Descr.begin(), o.Descr.end());
wchar_t message[100];
swprintf(message, 100, L"%s : %.4fms\n", widestr.c_str(), (float)o.Time / 1000000);
Logger::WriteMessage(message);

Logger::WriteMessage 来自 MSFT 单元测试框架 (using namespace Microsoft::VisualStudio::CppUnitTestFramework;)

在前两种情况下,我得到如下结果:

而不是预期的(案例3):

看起来存在指针问题,但代码看起来是正确的,尤其是使用 value 参数 (long long)。我错过了什么?

更新。正如 Omnifarious 建议的那样,将 static 用于局部变量,我在 v1 和 v3 中得到了不一致的结果(当一个接一个地使用时):

【问题讨论】:

    标签: c++ visual-studio c++11


    【解决方案1】:

    解开这一切有点棘手,但是,这里肯定有一个问题。这段代码:

    const wchar_t* ToWchar(string arg)
    {
        std::wstring widestr = std::wstring(arg.begin(), arg.end());
        return widestr.c_str();
    }
    

    返回一个指向死内存的指针。当函数退出并且widestr 超出范围时,它返回的指针将被释放。如果你把它改成这样:

    const wchar_t* ToWchar(string arg)
    {
        static ::std::wstring widestr;
    
        widestr = std::wstring(arg.begin(), arg.end());
        return widestr.c_str();
    }
    

    它可能会开始工作。但随后该函数将不再是可重入的,而且肯定不再是线程安全的。

    这个函数的情况完全相同:

    template<typename... T>
    wchar_t* Message(T &&... args)
    {
        wchar_t message[100];
        swprintf(message, 100, forward<T>(args)...);
        return message;
    }
    

    message 超出范围,您正在返回指向死堆栈空间的指针。同样,如果您将功能更改为:

    template<typename... T>
    wchar_t* Message(T &&... args)
    {
        static wchar_t message[100];
        swprintf(message, 100, forward<T>(args)...);
        return message;
    }
    

    它可能会开始工作,但不再是可重入或线程安全的。

    我会重新考虑您处理此问题的方式。这利用了 C++ 对临时对象生命周期的保证来处理问题:

    #include <string>
    #include <cstring>
    
    extern void fake_logger_writemessage(wchar_t const *);
    
    template <class T>
    class WCharWrapper {
     public:
        WCharWrapper() = delete;  // Make it unconstructable
    };
    
    template <>
    class WCharWrapper<wchar_t const *> {
     public:
        WCharWrapper() = delete;
        explicit WCharWrapper(wchar_t const *s) : s_(s) {}
    
        operator wchar_t const *() const { return s_; }
    
     private:
        wchar_t const * const s_;
    };
    
    template <>
    class WCharWrapper<::std::wstring const &> {
     public:
        WCharWrapper() = delete;
        explicit WCharWrapper(::std::wstring const &s) : s_(s) {}
    
        operator wchar_t const *() const { return s_.c_str(); }
    
     private:
        ::std::wstring const &s_;
    };
    
    template <>
    class WCharWrapper<char const *> {
     public:
        WCharWrapper() = delete;
        explicit WCharWrapper(char const *s) : s_(s, s + ::std::strlen(s)) {}
    
        operator wchar_t const *() const { return s_.c_str(); }
    
     private:
        ::std::wstring const s_;
    };
    
    template <>
    class WCharWrapper<::std::string const &> {
     public:
        WCharWrapper() = delete;
        explicit WCharWrapper(::std::string const &s) : s_(s.begin(), s.end()) {}
    
        operator wchar_t const *() const { return s_.c_str(); }
    
     private:
        ::std::wstring const s_;
    };
    
    template <typename T>
    T widen_strings(T &&arg)
    {
        return ::std::forward(arg);
    }
    
    WCharWrapper<char const *> widen_strings(char const *arg)
    {
        return WCharWrapper<char const *>(arg);
    }
    
    WCharWrapper<::std::string const &> widen_strings(::std::string const &arg)
    {
        return WCharWrapper<::std::string const &>(arg);
    }
    
    // Capture non-const as well, to make sure they aren't passed through unchanged.
    WCharWrapper<::std::string const &> widen_strings(::std::string &arg)
    {
        return WCharWrapper<::std::string const &>(arg);
    }
    
    template <typename T>
    T &&widen_strings(T && arg)
    {
        return arg;
    }
    
    template<typename... T>
    wchar_t const *Message(wchar_t *out, T &&... args)
    {
        swprintf(out, widen_strings(::std::forward<T>(args))...);
        return out;
    }
    
    template<typename... T>
    void LogMessage(T &&... args)
    {
        wchar_t msgbuf[100];
        fake_logger_writemessage(Message(msgbuf, ::std::forward<T>(args)...));
    }
    

    This version I put on Godbolt 应该可以完美运行。看看最后的例子,看看它是如何使用的。

    【讨论】:

    • 确实这解决了一些问题,但引入了另一个您没有提到的问题:3 个版本位于 for 循环中。如果我一直用“静态技巧”和 v3 说 v1,那么 v1 的结果是不一致的(字符串部分没有更新)并且 v3 是 changed (看起来是随机的)。查看更新。
    • @Soleil - 是的,我现在正在研究一个真正的解决方案。 static 是一个糟糕的 hack。
    • 我认为使用智能指针会有所帮助。或者是一种“部分垃圾收集器”。
    • 你能不能只更改函数以返回 wstring 而不是指针?
    • @Soleil - 我的事情并不完全正确。但我认为这是正确的道路,我已经没有时间了。 :-)
    猜你喜欢
    • 2011-09-26
    • 2011-02-12
    • 1970-01-01
    • 2012-01-26
    • 1970-01-01
    • 1970-01-01
    • 2017-04-09
    • 1970-01-01
    • 2018-12-02
    相关资源
    最近更新 更多