C++修改指向结构体的指针的向量会导致未定义行为

3

我有一个指向存储在另一个向量中的结构体的指针向量,这是我的学校项目。当我尝试使用指针更改结构体中的元素时,由于某种原因导致未定义的行为。我在下面剪裁了与该问题相关的部分代码。

#include <vector>
#include <string>
#include <iostream>

class someException{};

enum class ProcessStatus{
  RUNNING,
  READY
};

struct Process{
  int priority;
  std::string PID;
  ProcessStatus status;
  Process(){
    status = ProcessStatus::READY;
  }
};

struct ReadyList{
  std::vector<Process*> priority1;
  std::vector<Process*> priority0;
};

class ProcessManager{
private:
  std::vector<Process> processList;
  ReadyList readyList;
public:
  ProcessManager(){};

  void createProcess(std::string PID, int priority){
    Process process;
    process.priority = priority;
    process.PID = PID;
    if (priority == 0)
      process.status = ProcessStatus::RUNNING;
    processList.push_back(process);
    switch(priority){
      case 0:
        readyList.priority0.push_back(&processList.at(processList.size()-1));
        break;
      case 1:
        readyList.priority1.push_back(&processList.at(processList.size()-1));
        break;
      default:
        throw someException();
    }
    schedule(findRunningProcess());
  }

  void printProcesses(){
    std::cout<<"ReadyList results:"<<std::endl;
    for(auto &process: readyList.priority0){
      std::cout << "Process: "<< process->PID << " , Priority: "<<process->priority;
      if (process->status == ProcessStatus::RUNNING)
        std::cout << ", Status: RUNNING"<< std::endl;
      else
        std::cout <<", Status: READY"<<std::endl;
    }
    for(auto &process: readyList.priority1){
      std::cout << "Process: "<< process->PID << " , Priority: "<<process->priority;
      if (process->status == ProcessStatus::RUNNING)
        std::cout << ", Status: RUNNING"<< std::endl;
      else
        std::cout <<", Status: READY"<<std::endl;
    }
    std::cout<<"ProcessList results: "<<std::endl;
    for(auto &process: processList){
      std::cout << "Process: "<< process.PID << " , Priority: "<<process.priority;
      if (process.status == ProcessStatus::RUNNING)
        std::cout << ", Status: RUNNING"<< std::endl;
      else
        std::cout <<", Status: READY"<<std::endl;
    }
  }

private:
  void schedule(Process* currentProcess){
    Process* highestPriorityProcess;
    if (readyList.priority1.size()>0)
      highestPriorityProcess = readyList.priority1[0];
    else
      highestPriorityProcess = readyList.priority0[0];
    if (currentProcess->priority < highestPriorityProcess->priority){
      currentProcess->status = ProcessStatus::READY;
      highestPriorityProcess->status = ProcessStatus::RUNNING;
    }
  }

  Process* findRunningProcess(){
    for (auto &process: processList){
      if (process.status == ProcessStatus::RUNNING){
        return &process;
      }
    }
    return nullptr;
  }
};

int main(){
  ProcessManager pm = ProcessManager();
  pm.createProcess("ROOT", 0);
  std::cout<<"After creating process ROOT"<<std::endl;
  pm.printProcesses();
  pm.createProcess("A", 1);
  std::cout<<"After creating process A"<<std::endl;
  pm.printProcesses();
  return 0;
};

输出结果如下:
After creating process ROOT
ReadyList results:
Process: ROOT , Priority: 0, Status: RUNNING
ProcessList results: 
Process: ROOT , Priority: 0, Status: RUNNING
After creating process A
ReadyList results:
Process: ROOT , Priority: 0, Status: RUNNING
Process: A , Priority: 1, Status: RUNNING
ProcessList results: 
Process: ROOT , Priority: 0, Status: READY
Process: A , Priority: 1, Status: RUNNING

ProcessList设置为正确的值,进程A正在运行,进程ROOT处于就绪状态,但由于某些原因,ReadyList未发生变化。在我的原始代码中,就绪列表中进程ROOT的PID字符串值变为空,而我在这个例子中没有包含存储在进程中的映射值也被清除了。我还尝试直接更改readyList指针而不是使用findRunningProcess函数返回的指针,但这并没有解决PID和映射值的问题,反而导致了一些其他未定义的进程状态问题。我已经无法想出可能导致这种情况的原因,请帮忙!非常感谢。


6
当你执行 processList.push_back(process) 时,它可能会导致向量重新分配内存,从而使得你的所有指针都失效。 - M.M
2个回答

6
每次你执行以下操作时:
processList.push_back(process);

vector可能会重新调整大小,这意味着支持vector的数据存储将被复制到新的数据存储中然后丢弃。这会使您拥有另外两个vector,其中包含指向已释放并可能被重新分配内存的指针。

在使用processList时,可以使用std::liststd::deque,因为它们在增长时不会使指针失效。 std::deque应该具有更好的空间局部性能优势。

另一种选择是让另外两个vector存储processList中进程的索引,因为只要您只推回而不删除进程,它们就不会发生变化。

在任何情况下,从processList中删除进程而不确保首先从其他vector中删除它们都是不好的做法。std::deque在移除时处于劣势,如果你erase从中间删除进程,这将使指针无效。


1
deque 在增长时也不会使指针失效,因此它可能是更好的选择。 - Mark Ransom
1
@MarkRansom 哇,真是不好意思,我竟然错过了那个选项。 - user4581301
谢谢回答!所以如果我确实需要在processList的任何位置删除一个元素,而且我还要删除其他向量中相应的指针,那么我需要使用std::list而不是std::deque来防止指针失效?我在这里检查了一下:https://dev59.com/enA75IYBdhLWcg3wdIv7,上面说list应该可以很好地处理这个问题。 - B Wai
@BWai 是的。deque 的设计是尽可能减少在开头和结尾处添加和删除时的痛苦,但除非您告诉它,否则它不会触及中间的数据。但是一旦您告诉 deque 操作中间的数据,您不能再信任指针。使用 list 可以确保标准不会损坏指针。 list 非常简单易懂且接近无误,但速度可能较慢。我建议从 list 开始,先确定逻辑并使其正常工作,然后对程序进行分析,看看 list 是否足够快。 - user4581301
非常感谢!我使用了一个列表,一切都解决了!如果您不介意的话,我还有一个后续问题。我对C++还比较新手,不太确定引用和指针之间的区别,尽管我听说前者更安全。如果我使用引用的向量而不是指针,是否可以避免这个问题? - B Wai
@BWai 不是指针,它有一些限制。首先,在创建引用时,它必须指向某个对象,并且不能重新指向不同的对象。不幸的是,在创建引用之后,您可以无意中使引用所指向的内容无效,这就是您在此遇到的问题。 - user4581301

1
我怀疑你的ProcessManager中的进程列表向量在某个时刻被调整大小,导致它必须创建一个新的内部数据结构并复制内容,使旧指针失效。
http://en.cppreference.com/w/cpp/container/vector/push_back所述:
如果新的size()大于capacity(),则所有迭代器和引用(包括past-the-end迭代器)都会失效。否则,只有past-the-end迭代器会失效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接