当访问要推入并集向量的字符串的字符时,向量中的字符串会变得一团糟



这里有一个SSCCE(显示问题的简单示例):

#include <iostream>
#include <string>
#include <vector>
using namespace std;
enum Type {oInt, oFloat, oString, oArray};
struct Object; // fwd decl for union
union ObjectNative {
    int oInt;
    float oFloat;
    const char* oString;
    vector<Object>* oArray;
};
struct Object {
    Type type;
    ObjectNative obj;
};
vector<Object> exec(vector<string> tokens, vector<Object> stack);
void printStack(vector<Object> stack);
int main() {
    vector<string> tokens{"test", "another test", "even more test", "test test", "test test test", "lotsa test"};
    vector<Object> stack;
    stack = exec(tokens, stack);
    printStack(stack);
    return 0;
}
vector<Object> exec(vector<string> tokens, vector<Object> stack) {
    for (string s : tokens) {
        ObjectNative nObj;
        // !!!!!!!!!!!!!!!!!!!!!! THIS LINE !!!!!!!!!!!!!!!!!!!!!!
        s[0];
        nObj.oString = s.c_str();
        Object obj = Object{oString, nObj};
        stack.push_back(obj);
        // debugging lines
        printStack(stack);
        cout << "-------" << endl;
    }
    return stack;
}
void printStack(vector<Object> stack) {
    for (unsigned int i = 0; i < stack.size(); i ++) {
        Object o = stack[i];
        cout << o.obj.oString;
        cout << endl;
    }
}

注意我在exec函数中标记的行:

s[0];

我只是访问字符串中的第一个字符;我甚至没有用它做任何事情!然而,如果我注释掉那一行,我会得到正确的输出:

test
-------
test
another test
-------
(etc....)
-------
test
another test
even more test
test test
test test test
lotsa test

但有了这句话,我…呃,我不确定发生了什么。这是输出:

test
-------
ä7R
another test
-------
ä7R
even more test
even more test
-------
test test
ä7R
ä7R
test test
-------
test test
test test test
test test test
test test
test test test
-------
lotsa test
ä7R
ä7R
lotsa test
ä7R
lotsa test
-------
lotsa test
ä7R
ä7R
lotsa test
ä7R
lotsa test

代码似乎没有什么问题,但很明显我做错了什么。为什么矢量会被破坏成这样,它究竟是如何被不处理字符串的第一个字符所引起的?

string::c_str()返回的const char *的生存期是有限的。如果您试图保留这些字符串,则需要将它们复制到专用的存储中。

字符串s在每次迭代中被来自tokens的新字符串替换。当for循环在下一次迭代中将一个新字符串分配给s时,可以使上一次迭代的c_str()无效。

它起作用的事实可能是实现的一个幸运的侥幸:在引擎盖下,s可能从tokens中的条目中借用了字符串的存储空间。然而,对operator[]的调用可能导致它生成自己的字符串私有副本。这可以解释c_str()的行为差异。

要将副本保存在专用存储器中,您需要为其分配空间并将字符串复制到其中

nObj.oString = new char[s.size() + 1];  // allocate the space
std::strcpy( nObj.oString, s.c_str() ); // copy in the string

现在,从技术上讲,如果它包含ASCII NUL,它就不会复制到整个string中。你最初的代码并不关心这些,我上面的建议并没有改变这一点。:-)您可以在<cstring>中找到std::strcpy()

请注意,因为您已经为自己的存储分配了new[],所以在使用完delete[]后,您必须记住它,否则会出现内存泄漏。请确保使用delete[],而不是deletefree()

主要问题来自您的exec:

for (string s : tokens)
{
    ObjectNative nObj;
    nObj.oString = s.c_str();

从语义上讲,tokens向量是一个副本。此外,for (string s : tokens)s也是循环中的临时副本。当循环退出时,s将不再存在。所以当你做这样的事情时:

nObj.oString = s.c_str();

砰!您现在有一个悬空的const char *。现在您的程序有未定义的行为,因为之后您尝试在main中打印该堆栈。s[0];导致任何行为差异的事实在这一点上都是无关紧要的。

我会做什么来解决这个问题,取决于你试图做什么,如下所示:

  • 将原型更改为void exec(const vector<string> &tokens, vector<Object> &stack);
  • s修改为引用。例如CCD_ 31。只要tokens的寿命不超过stack,这应该是可以的

最新更新