为什么将 CString 转换为 wchar_t* 会产生一个临时副本?如果我们使用其他类型而不是 CString 会怎样?

Why does casting a CString to wchar_t* yield a temporary copy? What if we used some other type instead of CString?

PVS studio 发现了一个我正在调查的潜在缺陷;警告 V623 http://www.viva64.com/en/d/0240/

但是,没有足够的解释来理解为什么要制作临时副本。如果 CString::operator wchar_t*() 定义如下:

wchar_t* newbuf = new wchar_t[20];
// do things to put the current CString value in there
return newbuf;

然后是内存泄漏,但没有指针指向已销毁的临时文件。我只看到另一种可能的实现方式,即 return 包含在 CString 中的已经存在的 wchar_t* 指针,这不会创建新的临时指针。像这样:

return m_pContents;

那么,两个问题;在哪里可以找到 CString 的实际实现?而且,这个函数的什么有效实现(在任何自制类型上)可以使转换运算符 return 指向已销毁的临时对象?我只是不相信 Microsfoot 会像这样实现它:

CString tempCopy(*this);
return tempCopy.m_pContents;

这是 CString::operator LPCTSTRdocumentationLPCTSTRconst char*const wchar_t* 的奇特微软名称,具体取决于是否定义了 _UNICODE

描述如下:

Return Value

A character pointer to the string’s data.

Remarks

This useful casting operator provides an efficient method to access the null-terminated C string contained in a CString object. No characters are copied; only a pointer is returned. Be careful with this operator. If you change a CString object after you have obtained the character pointer, you may cause a reallocation of memory that invalidates the pointer.

那么,让我们继续回答您的问题。

where can I find the actual implementation for CString?

您可以在 Microsoft 的 C++ 运行时库的源文件中找到它。除非您在 Microsoft 工作,否则您可能无法访问。

I just don't believe that Microsfoot would implement it like:

CString tempCopy(*this);
return tempCopy.m_pContents;

我也不相信。那将是复制,文档明确指出没有完成复制。

现在标题中的问题:

Why does casting a CString to wchar_t* yield a temporary copy?

没有。好吧,它确实创建了一个指针的副本,但它没有创建包含字符串的数组的副本。

您对文章的理解有误。这是其中的损坏代码示例:

CString s1(L"1");
wchar_t s2[] = L"2";
bool a = false;
const wchar_t *s = a ? s1 : s2;

临时 CString 是从 s2 构建的。 ?: 运算符结束后,该临时文件不存在。使用指针并没有错,因为它是由 CString::operator wchar_t*() 编辑的 return。使用指针是错误的,因为 CString::operator wchar_t*() 是在临时 object 上调用的。临时 CString 不是由 CString::operator wchar_t*() 创建的。它是从 s2 隐式创建的,因为 s1CString.

类型

编辑。您在评论中提问:

So how did you know that a ? s1 : s2 returns a CString?

我知道无论布尔表达式如何,条件运算符都必须 return 相同的类型。我知道如果表达式 return 是不同的类型,并且一种类型可以隐式转换为另一种类型,那么运算符的 return 类型将是另一种类型。我知道 wchar_t* 可以转换为 CString。因此条件运算符将 return a CString.

嗯,它 可以 return CString,除了... CString 也可以转换为 wchar_t* ,所以条件运算符 可以 return wchar_t*CString 如果它 return CString,那么你最终得到一个悬空指针。你不能假设它 return 是什么,因为标准都没有保证。事实上,这使得程序 ill-formed 这是示例代码被破坏的另一种方式。这是标准的引述:

[expr.cond] §5.16 / 3(N3797 草案)

... it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed.

在您链接的 PVS 工作室代码中,您有一个三元运算符采用 wchar_t *CString。您的问题不在于转换运算符,而是三元将 wchar_t * 提升为 CString。您要么需要将结果分配给新的 CString,要么确保三元参数都是相同的类型,以便您获得引用。

"valid"(大空引号)的实施可能与

类似
class CString {
    wchar_t *data_;
    size_t max_size_;
    size_t size_;
    CString() : data_(new wchar_t[20]), max_data_(20), size_(0) {}

    //various operations on CString which might or might not do things like
    void extend(size_t new_size)
    {
        wchar_t *old_data = data_;
        data_ = new wchar_t[new_size];
        memcpy(data_, old_data_, size_);
        delete [] old_data;
        max_size_ = new_size;
    }
    //or even
    void append(wchar_t c)
    {
        data_[size_++] = c;
    }
    operator LPCSTR () const { data_[size_ + 1] = 0; return data_; }
};

因此,如果您使用强制转换运算符,您将获得一个指向空终止字符串的指针。直到你(说)向字符串添加一个字符。然后指针将指向一个非空终止字符串。或者,如果您向字符串中添加大量字符,它可以释放内存并将其指向其他位置。在这种情况下,您的指针指向可能看起来有效的内容,直到下一次内存分配发生并获取最近释放的内存。