0

I present to you this code riddle:

Using this compiler:

user@bruh:~/test$ g++ --version

g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

...and this compile string:

g++ main.cpp class.cpp -o main -g

...and these files:

class.hpp:

class base {

  public:

    base();

    virtual void f() = 0;
};

class derived : public base {

  public:

    derived( unsigned int id );

    void f() override;

    unsigned int id; 
};

class.cpp:

#include <iostream>

#include "class.hpp"

base::base() {

  return;
}

derived::derived( unsigned int id )
  :
  id( id ),
  base() {

  return;
}

void derived::f() {

  std::cout << "Ahoy, Captain! I am " << id << std::endl;

  return;
}

main.cpp:

#include <iostream>
#include <functional>
#include <vector>

#include "class.hpp"

int main() {

  unsigned int n_elements;

  std::cout << "enter the number of elements: ";

  std::cin >> n_elements;

  std::cout << std::endl;

  std::vector< class derived > v;

  std::vector< std::reference_wrapper< class base > > base_vector_0;

  std::vector< std::reference_wrapper< class base > > base_vector_1;

  for( unsigned int i = 0; i < n_elements; i++ ) {

    v.emplace_back( i );

    base_vector_0.emplace_back( v[ i ] );
  }

  for( unsigned int i = 0; i < n_elements; i++ ) {

    base_vector_1.emplace_back( v[ i ] );
  }

  std::cout << "sanity check:" << std::endl;

  for( unsigned int i = 0; i < n_elements; i++ ) {

    class base &base = v[ i ];

    base.f();
  }

  std::cout << "case 1:" << std::endl;

  for( unsigned int i = 0; i < n_elements; i++ ) {

    base_vector_1[ i ].get().f();
  }

  std::cout << "case 0:" << std::endl;

  for( unsigned int i = 0; i < n_elements; i++ ) {

    base_vector_0[ i ].get().f();
  }

  return 0;
}

...I get the following output:

user@bruh:~/test$ ./main
enter the number of elements: 1

sanity check:
Ahoy, Captain! I am 0
case 1:
Ahoy, Captain! I am 0
case 0:
Ahoy, Captain! I am 0
harrynh3@bruh:~/test$ ./main
enter the number of elements: 2

sanity check:
Ahoy, Captain! I am 0
Ahoy, Captain! I am 1
case 1:
Ahoy, Captain! I am 0
Ahoy, Captain! I am 1
case 0:
Segmentation fault (core dumped)

My questions:

  1. Why does this not segfault when the user supplied argument = 1?

  2. Why does this segfault when the user supplied argument > 1?

My short explanation of what the code does:

Creates many objects derived from an abstract base class. Stores references to the objects in containers as std::reference_wrapper around abstract base class reference. Creates the containers of std::reference_wrapper slightly differently. Calls the derived override of pure virtual function via the std::reference_wrappers. Segfaults specifically in the case denoted in the source code above.

I implore the C++ experts... Please help me! This is fascinating and I have no idea why it is happening! I've probably done something dumb.

4

2 回答 2

1

您在这段代码中创建了悬空引用:

  for( unsigned int i = 0; i < n_elements; i++ ) {

    v.emplace_back( i ); // [1]

    base_vector_0.emplace_back( v[ i ] ); // [2]
  }

[1] 添加新项目,[2] 存储对此项目的引用。如果在调用时重新构建了向量emplace_back,则所有引用都将失效,并且您引用了不存在的项目。 vector当超过当前容量时,通过添加新项目来重建。

如果您想准确存储n_elementsv向量中并避免重建向量,您可以调用reserve

  std::vector< class derived > v;
  v.reserve(n_elements); // added

  std::vector< std::reference_wrapper< class base > > base_vector_0;

  std::vector< std::reference_wrapper< class base > > base_vector_1;

  for( unsigned int i = 0; i < n_elements; i++ ) {

    v.emplace_back( i );

    base_vector_0.emplace_back( v[ i ] );
  }

现在,当emplace_back被调用时,没有引用无效,并且访问这些引用base_vector_0是安全的。

于 2019-03-01T06:47:08.190 回答
0

实际上,您的代码是这样做的:它将对临时的引用存储在向量中(封装在 a 中reference_wrapper);一旦控制退出循环体,在推动它之后,该引用立即变得悬空;接下来,您检索这个悬空引用并在其上调用一个虚拟成员函数,从而强制您的程序行为未定义;一旦你点燃了一个 UB,它就像一个奇点,所有的理性解释都停止了。

顺便说一句,控制最终会在到达它的主体端时退出一个函数,即使你没有把它放在return那里。0_o

于 2019-03-01T06:55:14.423 回答