1

我有一种情况,我正在执行一些项目的二进制序列化,并将它们写入一个不透明的字节缓冲区:

int SerializeToBuffer(unsigned char* buffer)
{
    stringstream ss;
    vector<Serializable> items = GetSerializables();
    string serializedItem("");
    short len = 0;
    for(int i = 0; i < items.size(); ++i)
    {
        serializedItem = items[i].Serialize();
        len = serializedItem.length();

        // Write the bytes to the stream
        ss.write(*(char*)&(len), 2);
        ss.write(serializedItem.c_str(), len);

    }
    buffer = reinterpret_cast<unsigned char*>(
                const_cast<char*>(ss.str().c_str()));
    return items.size();
}

从结果中删除const-nessss.str().c_str()然后将其分配给缓冲区是否安全?reinterpret_castunsigned char*

注意:代码只是为了让您了解我在做什么,它不一定编译。

4

5 回答 5

3

没有删除固有常量字符串的 const-ness 将导致Undefined Behavior

const char* c_str ( ) const;
获取等效的 C 字符串

生成与字符串对象具有相同内容的以空字符结尾的字符序列(c-string),并将其作为指向字符数组的指针返回。
自动附加终止空字符。
返回的数组指向一个内部位置,该位置具有该字符序列所需的存储空间及其终止的空字符,但该数组中的值不应在程序中修改,并且仅保证在下一次调用之前保持不变字符串对象的非常量成员函数。

于 2011-08-11T19:19:29.897 回答
1

简短的回答:没有

长答案:不。你真的不能那样做。这些对象的内部缓冲区属于对象。引用内部结构绝对是禁忌,并且会破坏封装。无论如何,这些对象(及其内部缓冲区)将在函数结束时被销毁,并且您的buffer变量将指向未初始化的内存。

使用const_cast<>通常表示您的设计中有问题。
使用reinterpret_cast<>通常意味着您做错了(或者您正在做一些非常低级的事情)。

你可能想写这样的东西:

std::ostream& operator<<(std::ostream& stream, Data const& serializable)
{
    return stream << serializable.internalData;

    // Or if you want to write binary data to the file:

    stream.write(static_cast<char*>(&serializable.internalData), sizeof(serializable.internalData);
    return stream;

}
于 2011-08-11T19:25:30.407 回答
1

这是不安全的,部分是因为您正在剥离const,但更重要的是因为您正在返回一个指向数组的指针,该数组将在函数返回时被回收。

当你写

ss.str().c_str()

的返回值仅在您调用它的对象仍然存在c_str()时才有效。string的签名stringstream::str()

string stringstream::str() const;

这意味着它返回一个临时string对象。因此,只要线路

ss.str().c_str()

执行完毕,临时string对象被回收。这意味着您收到的未完成指针c_str()不再有效,并且任何使用它都会导致未定义的行为。

要解决这个问题,如果你真的必须返回一个unsigned char*,你需要手动将 C 风格的字符串复制到它自己的缓冲区中:

/* Get a copy of the string that won't be automatically destroyed at the end of a statement. */
string value = ss.str();

/* Extract the C-style string. */
const char* cStr = value.c_str();

/* Allocate a buffer and copy the contents of cStr into it. */
unsigned char* result = new unsigned char[value.length() + 1];
copy(cStr, cStr + value.length() + 1, result);

/* Hand back the result. */
return result;

const此外,正如@Als 所指出的,如果您打算修改内容,那么剥离是一个坏主意。如果您不修改内容,那应该没问题,但是您应该返回 aconst unsigned char*而不是unsigned char*.

希望这可以帮助!

于 2011-08-11T19:25:51.967 回答
1

由于此函数的主要使用者似乎是 C# 应用程序,因此使签名对 C# 更友好是一个好的开始。如果我真的很忙并且没有时间做“正确的方式”的事情,这就是我会做的事情;-]

using System::Runtime::InteropServices::OutAttribute;

void SerializeToBuffer([Out] array<unsigned char>^% buffer)
{
    using System::Runtime::InteropServices::Marshal;

    vector<Serializable> const& items = GetSerializables();
    // or, if Serializable::Serialize() is non-const (which it shouldn't be)
    //vector<Serializable> items = GetSerializables();

    ostringstream ss(ios_base::binary);
    for (size_t i = 0u; i != items.size(); ++i)
    {
        string const& serializedItem = items[i].Serialize();
        unsigned short const len =
            static_cast<unsigned short>(serializedItem.size());

        ss.write(reinterpret_cast<char const*>(&len), sizeof(unsigned short));
        ss.write(serializedItem.data(), len);
    }

    string const& s = ss.str();
    buffer = gcnew array<unsigned char>(static_cast<int>(s.size()));
    Marshal::Copy(
        IntPtr(const_cast<char*>(s.data())),
        buffer,
        0,
        buffer->Length
    );
}

对于 C# 代码,这将具有签名:

void SerializeToBuffer(out byte[] buffer);
于 2011-08-11T20:28:29.533 回答
0

这是根本问题:

buffer = ... ;
return items.size();

在倒数第二行中,您正在为局部变量分配一个新值,该变量使用(直到该点)来保存您的函数作为参数给出的指针。然后,紧接着,您从函数返回,忘记了您刚刚分配给的变量的所有内容。那没有意义!

您可能想要做的是将数据从 指向的内存复制ss_str().c_str()存储在. 就像是buffer

memcpy(buffer, ss_str().s_str(), <an appropriate length here>)
于 2011-08-11T19:26:21.587 回答