为什么这个指针被错误地复制了?为什么段错误没有更早出现?

Why Is This Pointer Being Copied Incorrectly? Why Does The Segmentation Fault Not Occur Earlier?

我正在调试一个程序,该程序从二进制文件读取数据并将其放入 TaggerDataUnigram 对象的字段中,TaggerDataUnigram 是从 [=20= 派生的 class ].所有的读取操作读取文件中指定的若干个数据对象,并将这些对象放入TaggerData的字段中。因此,我定义了一个函数 ReadForNumberToRead,它接受一个文件和一个 Reader* 作为参数,Reader 是定义如何从文件中读取数据的仿函数的基础 class .每个 Reader 导数都接受一个 TaggerData* 作为参数,并将指针的值存储为成员。不幸的是,TaggerData 使用 getter 和 setter,但是 getter return 引用字段。因此,例如,OpenClassReader 通过 tagger_data_pointer_->getOpenClass().

访问 TaggerData::open_class

示例:ForbiddingRuleReader的构造函数:

ForbiddingRuleReader::ForbiddingRuleReader(
        FILE*& tagger_data_input_file_reference,
        TaggerData* tagger_data_pointer)
        : Reader(tagger_data_input_file_reference, tagger_data_pointer) {}

tagger_data_pointer_Readerprotected 成员。

Reader::Reader(FILE*& tagger_data_input_file_reference,
               TaggerData* tagger_data_pointer)
        : TaggerDataFileInputOutput(tagger_data_input_file_reference),
          tagger_data_pointer_(tagger_data_pointer) {} // tagger_data_pointer_ is initialized.

。 . .和 ArrayTagReader:

相同的构造函数
ArrayTagReader::ArrayTagReader(FILE*& tagger_data_input_file_reference,
                               TaggerData* tagger_data_pointer)
        : Reader(tagger_data_input_file_reference, tagger_data_pointer) {}

它们的用法也是一样的:

void TaggerDataUnigram::ReadTheForbiddingRules(
        FILE*& unigram_tagger_data_input_file_reference) {
    ForbiddingRuleReader forbidding_rule_reader(
            unigram_tagger_data_input_file_reference,
            this);
    ReadForNumberToRead(unigram_tagger_data_input_file_reference,
                        &forbidding_rule_reader);
}
[. . .]
void TaggerDataUnigram::ReadTheArrayTags(
        FILE*& unigram_tagger_data_input_file_reference) {
    ArrayTagReader array_tag_reader(unigram_tagger_data_input_file_reference,
                                    this);
    ReadForNumberToRead(unigram_tagger_data_input_file_reference,
                        &array_tag_reader);
}

不用说,TaggerDataUnigram 对象不会超出范围。


OpenClassReaderForbiddingRuleReader 都很好用;他们将文件的副本和 TaggerData* 存储为字段,并连续从文件中读取数据并将其放入 TaggerData 中的相应字段。当构造 ArrayTagReader 时,问题就出现了。尽管共享相同的构造函数并以与 ForbiddingRuleReader 相同的方式使用,但还是出现了严重错误——tagger_data_pointer_TaggerData* tagger_data_pointer 对象是用 !

构造的
Breakpoint 1, ArrayTagReader::ArrayTagReader (this=0x7fffffffd640, tagger_data_input_file_reference=@0x7fffffffd720: 0x62a730, tagger_data_pointer=0x7fffffffd8c0)
at array_tag_reader.cc:10
10      : Reader(tagger_data_input_file_reference, tagger_data_pointer) {}
(gdb) print tagger_data_pointer
 = (TaggerData *) 0x7fffffffd8c0 <----------
(gdb) continue
Continuing.

Breakpoint 2, ArrayTagReader::operator() (this=0x7fffffffd640) at array_tag_reader.cc:12
12  void ArrayTagReader::operator()() {
(gdb) print tagger_data_pointer_
 = (TaggerData *) 0x7fffffffd720 <----------

OpenClassReaderForbiddingRuleReader中,tagger_data_pointer_等于tagger_data_pointer

奇怪的是,错误不会立即产生,即使指针显然无效。

Breakpoint 3, ArrayTagReader::operator() (this=0x7fffffffd640) at array_tag_reader.cc:12
12  void ArrayTagReader::operator()() {
(gdb) print *tagger_data_pointer_
 = {_vptr.TaggerData = 0x62a730, open_class = std::set with 0 elements, forbid_rules = std::vector of length 275736, capacity -17591907707330 = {{tagi = -1972060027,
  [. . .]

但是,在第一次调用 TagIndexReader::operator() 时,程序遇到了分段错误,特别是 SIGSEGV。这并不奇怪;尽管 TagIndexReadertagger_data_pointer_ 是有效的,但 TaggerDataUnigram 对象的很大一部分已被泄露。

Breakpoint 4, TagIndexReader::operator() (this=0x7fffffffd650) at tag_index_reader.cc:7
7   void TagIndexReader::operator()() {
(gdb) print tagger_data_pointer_
 = (TaggerData *) 0x7fffffffd8c0 <---------- This is the correct value.
(gdb) print *tagger_data_pointer_
 = {_vptr.TaggerData = 0x41e5b0 <vtable for TaggerDataUnigram+16>, 
  open_class = std::set with 6467592 elements<error reading variable: Cannot access memory at address 0x5200000051>,

为什么 tagger_data_pointer 被错误地复制了?为什么程序在尝试写入无效内存后不会立即遇到段错误?我该如何解决这个问题? 谢谢你的时间。


更新: 这些可能有用:

void ArrayTagReader::operator()() {
    std::wstring array_tag = Compression::wstring_read(
            tagger_data_file_reference_);
    tagger_data_pointer_->getArrayTags().push_back(array_tag);
}

void ReadForNumberToRead(
        FILE* tagger_data_input_file_reference,
        Reader* pointer_to_a_reader) {
    for (int unsigned number_to_read =
         Compression::multibyte_read(tagger_data_input_file_reference);
         number_to_read != 0;
         --number_to_read) {
        pointer_to_a_reader->operator()();
  }
}

更新: 不知何故,我错过了 ArrayTagReadertagger_data_poiner_ 的声明;使指针 const 产生了编译器错误,引起了我的注意。我仍然不明白的是为什么:

  1. 编译器没有抱怨使用了未初始化的指针。
  2. 该程序在尝试修改时没有遇到分段错误,例如tagger_data_poiner_->getArrayTags().

Despite sharing an identical constructor and being used the same way as ForbiddingRuleReader

我不确定你为什么认为这些很重要,但我可以告诉你,根据 C++ 标准,这些与两种类型是否具有相同的内存布局或一个是否可以 reinterpret_cast(或道德等价物)在他们之间。

我无法正确阅读您的代码,因为除了 "UPDATE" 之外的所有内容都非常混乱,除了作者之外的任何人都很难阅读。更新部分似乎没问题。因此,我将简要介绍一些使用指针进行复制的技巧(因为我最近看到很多人都犯了这些错误),也许它会有所帮助。

  1. 确保您不只是从或复制到非 "marked" 分配的内存位置。换句话说,如果您有一个指针并且您只是将数组中的数据复制到它指向的内存位置,则没有什么可以阻止您的程序或计算机上当前 运行 的其他程序修改该区域。您首先分配 space(使用 new、malloc 等),然后您可以复制 from/to 它。

    type *p = new type[size];

  2. 即使您满足了第 1 点,请确保复制的 space 不超过 size.

关于这个问题的建议,通过评论(我不能评论 ATM)...

你可能是一个非常优秀的程序员。但是你会犯错误。这意味着你必须找到它们。这意味着您应该保持代码整洁。但要保持整洁还有更多 至关重要的 原因。阅读您的代码的人并不真正知道所有东西 "should" 在哪里。对于他们来说,阅读乱码是不可能完成的任务。这很重要,因为有人可能必须继续为公司处理您的代码。或者,如果您正在寻求其他程序员的帮助,就像您现在正在做的那样,您需要有人来帮助您。

对于代码中的每个子块(用 { } 覆盖),缩进应为 1 个制表符或 4 个 spaces(您不能在 Whosebug 上使用制表符),除非该块是空.

如果一条指令由于长度原因在下一行继续,它也应该缩进。在这种情况下,您还可以添加其他选项卡或 spaces 以使一切看起来都不错。例如,如果您有一个足够长的方程式,可以分成 3 行,则可以让每一行都从第一行的“=”开始。

"UPDATE" 部分看起来比其他部分好得多,但您仍应使用 4-space 缩进而不是 2-space 缩进。

"tagger_data_pointer_TaggerData* tagger_data_pointer 构造对象所指向的内存位置不同"

这通常意味着该值已被覆盖。一个非常常见的原因是前一个字段中的缓冲区溢出,或者不太常见的是后一个字段中的 _under_flow。它还解释了为什么这个问题只出现在您的两个 class 之一中;另一个 class 有其他邻居。尽管如此,并非所有覆盖都是缓冲区 over/underflows。无效类型转换是另一个可能的问题。

既然你不想改变指针,那就去做吧const。第二种调试技术是用 3 个相同副本的数组替换该字段。创建一个函数,检查所有三个是否相同,如果不相同则抛出,否则 return 单个值。在取消引用指针的地方,您现在调用此检查函数。这为您提供了一个很好的机会来检测更改的确切性质。更奇特的算法会添加具有已知值的额外填充数据。