Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/156.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 结构破坏中的怪异行为_C++_Serialization_Struct_File Writing - Fatal编程技术网

C++ 结构破坏中的怪异行为

C++ 结构破坏中的怪异行为,c++,serialization,struct,file-writing,C++,Serialization,Struct,File Writing,我正在尝试将结构写入文件并将其读回。执行此操作的代码如下所示: #include <fstream> #include <iostream> #include <cstring> using namespace std; struct info { int id; string name; }; int main(void) { info adam; adam.id = 50; adam.name = "adam"; ofstr

我正在尝试将结构写入文件并将其读回。执行此操作的代码如下所示:

#include <fstream>
#include <iostream>
#include <cstring>

using namespace std;

struct info {
  int id;
  string name;
};

int main(void) {
  info adam;
  adam.id = 50;
  adam.name = "adam";

  ofstream file("student_info.dat", ios::binary);
  file.write((char*)&adam, sizeof(info));
  file.close();

  info student;
  ifstream file2("student_info.dat", ios::binary);
  file2.read((char*)&student, sizeof(student));
  cout << "ID =" << student.id << " Name = " << student.name << endl;

  file2.close();
  return 0;
}
在查看核心转储时,我看到在销毁结构信息时发生了一些奇怪的事情

(gdb) bt
#0  0x00007f035330595c in ?? ()
#1  0x00000000004014d8 in info::~info() () at binio.cc:7
#2  0x00000000004013c9 in main () at binio.cc:21
我怀疑字符串销毁过程中发生了一些奇怪的事情,但我无法找出确切的问题。任何帮助都会很好


我使用的是gcc 8.2.0。

你不能那样序列化/反序列化。在这一行:

file2.read((char*)&student, sizeof(student));
您只是在
info
的一个实例上写了1:1,其中包括一个
std::string
。这些不仅仅是字符数组——它们在堆上动态分配存储,并使用指针进行管理。因此,如果您这样覆盖该字符串,它将变得无效,这是未定义的行为,因为它的指针不再指向有效位置

相反,您应该保存实际字符,而不是字符串对象,并在加载时使用该内容生成一个新字符串


通常,您可以对琐碎的对象进行这样的复制。您可以这样测试它:

std::cout << std::is_trivially_copyable<std::string>::value << '\n';

std::cout当
adam.name=“adam”完成后,会在内部为
adam.name
分配适当的内存


file2.read((char*)和student时,sizeof(student))完成后,您将在内存位置(即地址
&student
处)写入,该地址尚未正确分配以容纳正在读取的数据<代码>学生。adam
没有为其分配足够的有效内存。在这样做时,将
读入
学生
对象的位置实际上会导致内存损坏。

添加到可接受的答案中,因为询问者仍然对“为什么删除第一个对象时会崩溃”感到困惑:

让我们看一下分解,因为它不能说谎,即使面对展示UB的错误程序(与调试器不同)

(请注意,
rsp
——我们的堆栈指针——除了在
main
的开头和结尾进行调整外,从未改变过)

下面是adam的初始化:

    lea     rax, [rsp+24]
    // ...
    mov     QWORD PTR [rsp+16], 0
    mov     QWORD PTR [rsp+8], rax
    mov     BYTE PTR [rsp+24], 0
   call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long)
似乎
[rsp+16]
[rsp+24]
保存字符串的大小和容量,而
[rsp+8]
保存指向内部缓冲区的指针。该指针设置为指向字符串对象本身

然后,
adam.name
“adam”
覆盖:

注意
student
的缓冲区指针如何指向
student
以表示一个小缓冲区

现在,您残酷地将
student
的内部结构替换为
adam
的内部结构。突然,
student
的缓冲区指针不再指向预期的位置。这是个问题吗

    mov     rdi, QWORD PTR [rsp+56]
    lea     rax, [rsp+72]
    cmp     rdi, rax
    je      .L90
    call    operator delete(void*)
是的!如果
student
的内部缓冲区指向我们最初设置它的地方以外的任何地方(
rsp+72
),它将
删除该指针。此时,我们不知道
adam
的缓冲区指针(您复制到
student
中)指向的确切位置,但肯定是错误的位置。如上所述,
“adam”
很可能仍被小字符串优化所覆盖,因此
adam
的缓冲区指针可能与之前完全相同:
rsp+24
。因为我们把它复制到<代码>学生<代码>中,它不同于<代码> RSP+72 ,我们称之为“代码>删除(RSP+24)——这是在我们自己的栈的中间。环境并不认为这很有趣,在第一次解除分配中,你就得到了一个segfault(第二次解除分配甚至不会删除任何东西,因为那里的世界仍然很好-
adam
没有受到你的伤害)


一句话:不要试图智取编译器(“它不会出错,因为它将在同一堆上!”)。你会输的。遵守语言规则,没有人会受伤


旁注:
gcc
中的这种设计甚至可能是有意的。我相信它们可以同样轻松地存储
nullptr
,而不是指向string对象来表示一个小字符串缓冲区。但在这种情况下,您不会将错误与这种不当行为区分开来。

有趣的是,这适用于
clang
7.0和
libc++
,可能是因为字符串内容适合它们的SSO缓冲区。一般来说,您不能这样做,这是正确的,但有时您可以这样做,尤其是当结构为,两者都有各自指向堆的指针,现在您可以将其中一个指针设置为指向另一个的堆内存。在程序结束时,两者都试图释放相同的内存,这导致了问题。至少这是未定义行为的一种表现方式,它实际上取决于特定的编译器/安装程序如何在后台实现它,字符串有多大,可能还有更多。@Blaze事实上,我试图找出错误发生的确切对象。我怀疑双重释放问题,但令我惊讶的是
student
对象破坏导致了该故障,而
adam
尚未被破坏。@lubgr“works”是未定义行为的最糟糕症状,特别是像小字符串一样的@AlanBirtles的可能重复,另一个问题是在不同的过程中读写。我知道string对象将在堆中存储指向其内容的指针,当在单独的进程中读取时,它将崩溃。然而,在我的问题中,阅读和写作是在同一个过程中发生的。它的行为仍然没有定义,你正在创建两个指向相同数据的对象,解决方案是相同的。这很好地回答了我的问题。非常感谢:)
    lea     rax, [rsp+72]
    // ...
    mov     QWORD PTR [rsp+64], 0
    // ...
    mov     QWORD PTR [rsp+56], rax
    mov     BYTE PTR [rsp+72], 0
    mov     rdi, QWORD PTR [rsp+56]
    lea     rax, [rsp+72]
    cmp     rdi, rax
    je      .L90
    call    operator delete(void*)