为什么此指针被错误地复制?为什么分段错误没有更早发生

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

本文关键字:错误 为什么 指针 复制 分段      更新时间:2023-10-16

我正在调试一个程序,该程序从二进制文件中读取数据并将其放入TaggerDataUnigram对象的字段中,TaggerDataUnigram是从TaggerData派生的类。所有读取操作都会读取文件中指定的许多数据对象,并将这些对象放入 TaggerData 的字段中。因此,我定义了一个函数ReadForNumberToRead,它将文件和Reader*作为参数,Reader函数的基类,用于定义如何从文件中读取数据。每个Reader导数都采用TaggerData*作为参数,并将指针的值存储为成员。不幸的是,TaggerData使用 getter 和 setter,但 getter 返回对字段的引用。因此,例如,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
$1 = (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_
$2 = (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_
$3 = {_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_
$16 = (TaggerData *) 0x7fffffffd8c0 <---------- This is the correct value.
(gdb) print *tagger_data_pointer_
$17 = {_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() .
"

tagger_data_pointer_ 不指向内存中与构造对象时使用的TaggerData* tagger_data_pointer相同的位置"

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

既然你不是要改变指针,那就让它const。第二种调试技术是将字段替换为包含 3 个相同副本的数组。创建一个函数,该函数检查所有三个是否相同,如果不是,则抛出,否则返回单个值。在取消引用指针的位置,现在调用此检查函数。这为您提供了检测更改的确切性质的好机会。更花哨的算法会添加具有已知值的额外填充数据。

尽管共享相同的构造函数并且使用方式与ForbiddingRuleReader相同

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

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

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

    type *p = new type[size];

  2. 即使您满足第 1 点,也要确保复制的空间不超过 size

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

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

对于

代码中的每个子块(用 { } 覆盖),缩进应该是 1 个制表符或 4 个空格(你不能在 StackOverflow 上使用制表符),除非该块为空。

如果指令由于长度而在下一行继续,它也应该缩进。在这种情况下,您还可以添加其他制表符或空格,以使所有内容看起来都不错。例如,如果你有一个足够长的公式,可以分成 3 行,你可以让每一行从第一行的"="开始。

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