【问题标题】:C++ modifying vector of pointers to structs cause undefined behaviorC ++修改指向结构的指针向量导致未定义的行为
【发布时间】:2017-06-10 03:54:55
【问题描述】:

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

#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 未更改。在我的原始代码中,readylist 中进程 ROOT 的 PID 字符串值变为空,并且我在此示例中遗漏的进程中存储的映射值在更改状态后也被清除。 我还测试了直接使用 readyList 指针进行更改,而不是使用 findRunningProcess 函数返回的指针,这并没有解决 PID 和映射值的问题,而是导致进程状态中的一些其他未定义行为。我对可能导致这种情况的原因一无所知,请帮忙!非常感谢。

【问题讨论】:

  • 当您执行processList.push_back(process) 时,可能会导致向量重新分配,从而使您的所有指针悬空

标签: c++ pointers vector struct


【解决方案1】:

每次你:

processList.push_back(process);

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

processList 是使用std::liststd::deque 的好地方,因为它们不会随着指针的增长而失效。 std::deque 应该具有一些性能优势,因为它往往具有更好的空间局部性。

另一种方法是让另外两个vectors 将进程的索引存储在processList 中,因为只要您只推回而不删除进程,它们就不会更改。

在任何一种情况下,从processList 中删除进程而不确保它们已首先从其他vectors 中删除将是一个坏主意。如果您erase a process from the middlestd::deque 在删除方面处于不利地位,因为这将使指针无效。

【讨论】:

  • deque 在增长时也不会使指针失效,因此它可能是一个更好的选择。
  • @MarkRansom 哇。很遗憾我错过了那个选项。
  • 感谢您的回答!因此,如果我确实需要擦除 processList 中任何位置的元素,考虑到我还将删除其他向量中的相应指针,我将需要使用 std::list 而不是 std::deque 来防止指针失效?我在这里查看了:stackoverflow.com/questions/3287801/…,它说该列表应该可以很好地处理这个问题。
  • @BWai 是的。 deque 旨在以尽可能少的痛苦从头到尾添加和删除,但除非您告诉它,否则它不会触及中间的数据。但是一旦你告诉deque 在中间操作,你就不能再相信指针了。使用list,该标准可确保您不会损害任何指针。 list 非常简单,几乎是万无一失的,但可能很慢。我建议从list 开始,理清逻辑并使其正常工作,然后分析程序以查看list 是否足够快。
  • 非常感谢!我使用了一个列表,它解决了所有问题!如果您不介意,还有一个后续问题。我对 c++ 还是有点陌生​​,不太确定引用和指针之间的区别,尽管我听说前者更安全。如果我使用引用向量而不是指针,我会避免这个问题吗?
【解决方案2】:

我怀疑您在 ProcessManager 中的进程列表向量在某个时候正在调整大小,导致它必须创建一个新的内部数据结构并复制内容,从而使您的旧指针悬空。

如此处所述:http://en.cppreference.com/w/cpp/container/vector/push_back

如果新的 size() 大于 capacity() 则所有迭代器和 引用(包括过去的迭代器)无效。 否则只有过去的迭代器无效。

【讨论】:

    猜你喜欢
    • 2010-10-05
    • 2012-02-07
    • 2018-08-28
    • 1970-01-01
    • 2017-04-27
    • 2017-01-04
    • 1970-01-01
    • 2021-10-15
    相关资源
    最近更新 更多