【问题标题】:writing directly to std::string internal buffers直接写入 std::string 内部缓冲区
【发布时间】:2009-06-25 09:44:41
【问题描述】:

我正在寻找一种将一些数据填充到跨 DLL 边界的字符串中的方法。因为我们使用不同的编译器,所以我们所有的 dll 接口都是简单的 char*。

是否有正确的方法将指针传递给 dll 函数,以便它能够直接填充字符串缓冲区?

string stringToFillIn(100, '\0');
FunctionInDLL( stringToFillIn.c_str(), stringToFillIn.size() );   // definitely WRONG!
FunctionInDLL( const_cast<char*>(stringToFillIn.data()), stringToFillIn.size() );    // WRONG?
FunctionInDLL( &stringToFillIn[0], stringToFillIn.size() );       // WRONG?
stringToFillIn.resize( strlen( stringToFillIn.c_str() ) );

看起来最有希望的是 &stringToFillIn[0],但如果您认为 string::data() == &string[0],那么这是一种正确的方法吗?好像不一致。

或者最好吞下额外的分配并避免这个问题:

vector<char> vectorToFillIn(100);
FunctionInDLL( &vectorToFillIn[0], vectorToFillIn.size() );
string dllGaveUs( &vectorToFillIn[0] );

【问题讨论】:

    标签: c++ string


    【解决方案1】:

    我不确定标准是否保证std::string 中的数据存储为char*。我能想到的最便携的方式是使用std::vector,它保证将其数据存储在连续的内存块中:

    std::vector<char> buffer(100);
    FunctionInDLL(&buffer[0], buffer.size());
    std::string stringToFillIn(&buffer[0]);
    

    这当然需要将数据复制两次,效率有点低。

    【讨论】:

    • 就效率而言,如果您开始使用 std::vector 作为缓冲区,您将遇到不同类型的性能问题,其中向量的每个元素都被一个一个地初始化。如果您保留一个 32K 的缓冲区(不是那么多),您将花费大量 CPU 时间来初始化此缓冲区。如果你只需要一块连续的内存,你最好简单地使用一个数组 new char[] 将它与 std::unique_ptr 或其他一些 RAII 模式结合起来,你很高兴但不要使用 std::向量,除非你绝对需要初始化每个元素。
    • 使用stackoverflow.com/questions/11149665/…中的vector技巧。
    • "我不确定标准是否保证std::string 中的数据存储为char*。"这是有保证的。 std::string 使用 charen.cppreference.com/w/cpp/string/basic_string
    【解决方案2】:

    更新(2021 年):C++11 清除了这一点,此处表达的担忧不再相关。

    经过大量阅读和挖掘,我发现string::c_strstring::data 可以合法地返回一个指向缓冲区的指针,该缓冲区与字符串本身的存储方式无关。例如,字符串可能存储在段中。写入这些缓冲区会对字符串的内容产生未定义的影响。

    此外,string::operator[] 不应用于获取指向字符序列的指针 - 它应仅用于单个字符。这是因为指针/数组等价性不适用于字符串。

    这样做非常危险的是,它可以在某些实现上运行,但在未来某个日期会突然无缘无故地崩溃。

    因此,正如其他人所说,这样做的唯一安全方法是避免任何尝试直接写入字符串缓冲区并使用向量,将指针传递给第一个元素,然后从向量上分配字符串从 dll 函数返回。

    【讨论】:

    • C++0x 正在更改字符串以使用连续内存
    • 帕特里克怎么说。此外,Herb Sutter 在 2008 年与 C++0x 工作组讨论这个问题时,不知道有一个不连续的实现:herbsutter.wordpress.com/2008/04/07/…(向下滚动到 cmets)。
    • 有趣的是,他说 &str[0] 应该给出连续数据,Stroustrup 在“C++ 编程语言特别版”20.3.3 p585 中似乎另有说法。也许我误解了它。滚上 C++0x 来收拾烂摊子!我们认为我们在代码的各个部分都依赖于非技术保证的行为,因此我们将添加一些测试,如果我们对字符串的假设不再成立,这些测试将会中断。叹息。
    • 20.3.3(在我的第 3 版中)中的那一点略有不同——数组/指针等价也不适用于向量。我不认为 Sutter 与 Stroustrup 相矛盾,并且在任何情况下,在编写参考作品时,您都希望尽可能避免使用“目前,我看过的所有实现都做 X”形式的陈述,以支持“标准保证 Y,不要假设 X”。当您讨论标准的未来版本和/或编写只需要在 Sutter 知道的实现上工作的实际代码时,情况会有所不同......
    • 不过,我同意您的“在现实生活中进行测试”的方法。如果在使用时断言 &s[0] + (s.size()-1) == &s[s.size()-1] 那么你应该是好的,尽管我认为这可能是由于史诗般的厄运即使字符串存储在多个单独的分配中也是如此。对于从 0 到 length()-1 的所有 n,向量的保证是 &v[n] == &v[0] + n。
    【解决方案3】:

    在 C++98 中,您不应更改 string::c_str()string::data() 返回的缓冲区。此外,正如其他答案中所解释的,您不应使用 string::operator[] 来获取指向字符序列的指针 - 它只能用于单个字符。

    从 C++11 开始,字符串使用连续内存,因此您可以使用 &amp;string[0] 访问内部缓冲区。

    【讨论】:

      【解决方案4】:

      只要 C++11 提供连续的内存保证,在生产实践中这种“hacky”方法就非常流行:

      std::string stringToFillIn(100, 0);
      FunctionInDLL(stringToFillIn.data(), stringToFillIn.size());
      

      【讨论】:

      • 感谢 Orion Edwards 的编辑。请注意,它现在仅适用于 C++17 标准。详情请见en.cppreference.com/w/cpp/string/basic_string/data
      • 只是为了扩展 Brian 的评论,C++17 添加了一个非常量 data() 覆盖,专门允许这种行为。
      【解决方案5】:

      考虑到 Patrick 的评论,我想说的是,直接写入 std::string 是可以且方便/高效的。我会使用&amp;s.front() 来获得char *,就像在这个mex 示例中一样:

      #include "mex.h"
      #include <string>
      void mexFunction(
          int nlhs,
          mxArray *plhs[],
          int nrhs,
          const mxArray *prhs[]
      )
      {
          std::string ret;
          int len = (int)mxGetN(prhs[0]);
          ret.reserve(len+1);
          mxGetString(prhs[0],&ret.front(),len+1);
          mexPrintf(ret.c_str());
      }
      

      【讨论】:

      • string.front() 不应在空字符串上调用;请改用&amp;string[0]
      • 对我来说,这需要ret.resize 而不是ret.reserve
      【解决方案6】:

      我不会构造一个std::string 并发送一个指向跨 dll 边界的内部缓冲区的指针。相反,我会使用一个简单的char 缓冲区(静态或动态分配)。在对 dll 的调用返回后,我会让std::string 接管结果。让被调用者写入内部类缓冲区在直觉上是错误的。

      【讨论】:

        【解决方案7】:

        您可以使用在 unique_ptr 中分配的 char 缓冲区而不是向量:

        // allocate buffer
        auto buf = std::make_unique<char[]>(len);
        // read data
        FunctionInDLL(buf.get(), len);
        // initialize string
        std::string res { buf.get() };
        

        您不能使用提到的方式直接写入字符串缓冲区,例如 &str[0] 和 str.data():

        #include <iostream>
        #include <string>
        #include <sstream>
        
        int main()
        {
            std::string str;
            std::stringstream ss;
            ss << "test string";
            ss.write(&str[0], 4);       // doesn't working
            ss.write(str.data(), 4);    // doesn't working
            std::cout << str << '\n';
        }
        

        直播example.

        【讨论】:

          【解决方案8】:

          你们都已经解决了连续性问题(即不能保证是连续的),所以我只提一下分配/解除分配点。过去我遇到过在 dll 中分配内存(即让 dll 返回一个字符串)的问题,这些问题在销毁时(在 dll 之外)导致错误。要解决此问题,您必须确保您的分配器和内存池在 dll 边界上保持一致。它会为您节省一些调试时间;)

          【讨论】:

            【解决方案9】:

            std::string 的标准部分是 API 和一些行为,而不是实现的内存布局。

            因此,如果您使用不同的编译器,则不能假设它们是相同的,因此您需要传输实际数据。正如其他人所说,传输字符并推入新的std::string

            【讨论】:

              猜你喜欢
              • 2017-01-05
              • 2013-06-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-09-06
              • 2016-12-06
              • 1970-01-01
              相关资源
              最近更新 更多