【问题标题】:Question related to std::string object and c_str() method in 3 different implementations与 3 种不同实现中的 std::string 对象和 c_str() 方法相关的问题
【发布时间】:2021-07-27 01:51:45
【问题描述】:

前几天我看到了一个奇怪的行为。 所以我想将行(存在于向量中)存储在 char 数组中,并想使用 '\n' 作为分隔符。

我知道字符串类中的 c_str() 方法返回一个指向以 '\0' 结尾的 char 数组的指针。

基于我对 C++ 的经验/理解。(请参阅 greet0 和 greet2 函数)。 我认为它应该可以工作,但它没有。

谁能解释三个 greet 函数中的不同行为?每个 greet 函数中提到的对象的范围是什么? (我也猜想字符串对象在 greet1 函数中被破坏了,但如果是这样的话,cout

//The snippet that where i first encountered the issue. 
const char* concatinated_str(std::vector<std::string> lines, const char *delimiter)
{
        std::stringstream buf;
        std::copy(lines.begin(), lines.end(), std::ostream_iterator<std::string>(buf, delimiter));
        string w = buf.str();
        const char *ret = w.c_str();

        return ret;

}
//Implementation 0
string greet0(){
    string msg = "hello";
    return msg;
}

//Implementation 1
const char* greet1(){
    string msg = "hello";
    cout<<&msg<<endl;
    return msg.c_str();
}

//Implementation 2
const char* greet2(){
    const char* msg = "hello";
    return msg;
}


int main(){
    auto w0 = greet0();
    cout<<&w0<<endl;
    cout<<"greet0:"<<w0<<endl;

    auto w1 = greet1();
    cout<<"greet1:"<<w1<<endl;
    
    const char* w2 = greet2();
    cout<<"greet2:"<<w2<<endl;
}

输出:

0x7fff0ff3e8e0
0x7fff0ff3e8e0
greet0:hello
greet1:
greet2:hello

【问题讨论】:

  • cout&lt;&lt;&amp;w0&lt;&lt;endl; cout&lt;&lt;&amp;msg&lt;&lt;endl; 您正在输出指向 std::string 的指针。删除 &
  • 当您的程序尝试对它无权使用的内存进行操作时,您只会收到“分段错误”。在这种情况下你不会得到一个,因为字符串的内存要么在你的堆栈上(短字符串优化),要么它使用的堆内存被释放但没有返回给操作系统(这是一种内存分配优化,用于加速未来malloc / 新操作。)

标签: c++ string pointers local-variables dangling-pointer


【解决方案1】:

按值返回std::string 或指向字符串文字的指针非常好。
使用greet1() 的返回值虽然具有未定义的行为,因为您尝试打印其元素的std::string 在其封闭函数的末尾死亡,使返回的指针悬空。

如果取消引用未定义的悬空指针会发生什么情况,就像您有一个指向空字符串的指针一样,因为存储被重用是一种更良性的可能性。

顺便说一句,std::string 的地址对于执行您的程序的人来说很少有那么有趣,尽管打印它非常好。

【讨论】:

    【解决方案2】:

    在声明cout&lt;&lt;&amp;w0&lt;&lt;endl; cout&lt;&lt;&amp;msg&lt;&lt;endl;您正在输出指向std::string 的指针。删除 & 以实际打印字符串,而不是其地址。如果您对两个不同对象的相同结果感到困惑,那可能是因为它们是局部变量的地址。内存可以重复使用,因为这些对象在其生命周期内是有限的,不必具有唯一的位置。

    greet0 技术上msg 是一个局部变量,并在退出函数时停止存在,但编译器可能会优化返回值,而不是将msg 复制到外部,实际代码将在目标@987654328 形成一个适当的对象@。使用较新的编译器可以保证返回值优化。

    在函数中

    const char* greet1(){
        string msg = "hello";
        cout<<&msg<<endl;
        return msg.c_str();
    }
    

    msg 这里是一个函数局部变量,所以它表示一个对象在包含它的作用域结束时停止存在,即在函数返回之后。在return 行之后,取自c_str() 的指针悬空,因为该方法返回指向std::string 内部存储的指针。 msg 的存储已被破坏,您正在通过访问它来调用 Undefined Behaviour。分段错误(顺便说一下,这纯粹是 Linux 事件,Windows 中的机制不同)是可能的结果,但不是必需的。

    在第三个函数中

    const char* greet2(){
        const char* msg = "hello";
        return msg;
    }
    

    msg 指向一个包含常量字符串“hello”的数组。由字符串字面量创建的常量字符串与全局静态对象具有相同的寿命。这些字符串是在编译期间形成的。退出函数不会使指针失效,您仍然可以取消引用它,因为字符串仍然存在。

    【讨论】:

    • ...无论如何都要访问它。
    【解决方案3】:

    调用未定义行为的唯一代码与此函数有关

    #Implementation 1
    const char* greet1(){
        string msg = "hello";
        cout<<&msg<<endl;
        return msg.c_str();
    }
    

    退出函数后,std::string 类型的本地对象msg 将不再存在。它将被摧毁。所以函数返回一个无效的指针。

    在这个函数实现中

    #Implementation 2
    const char* greet2(){
        const char* msg = "hello";
        return msg;
    }
    

    返回一个指向字符串文字"hello" 的第一个字符的指针,该字符具有静态存储持续时间。这意味着退出函数后字符串文字将是活动的。因此该函数返回一个有效的指针。

    这个函数

    #Implementation 0
    string greet0(){
        string msg = "hello";
        return msg;
    }
    

    返回一个 std::string 类型的临时对象,该对象被移动(可能使用移动省略)到 main 中的变量 w0

    auto w0 = greet0();
    

    所以这个函数是正确的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-04
      • 1970-01-01
      • 2022-10-27
      • 1970-01-01
      • 1970-01-01
      • 2012-04-17
      • 2014-09-03
      • 1970-01-01
      相关资源
      最近更新 更多