Problema com erro de liberação de memória encontrado em std::queue

Há um requisito no projeto para usar std::queue para processar eventos de mensagens sequencialmente.

Um exemplo simples é o seguinte:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

struct MyEvent {

  MyEvent() { event_ = CreateEvent(nullptr, 0, 0, 0); }

  ~MyEvent() { std::cout << "MyEvent deconstruct" << std::endl; }

  void Run() {

    if (event_ != nullptr) {

      SetEvent(event_);

    }

  }

 private:

  HANDLE event_;

};

int main() {

  std::queue<MyEvent> my_event_queue;

  HANDLE event = CreateEvent(nullptr, 0, 0, 0);

  for (int i = 0; i < 3; i++) {

    auto task = new MyEvent();

    my_event_queue.push(*task);

  }

  while (!my_event_queue.empty()) {

    auto my_event = &my_event_queue.front();

    my_event_queue.pop();

    delete my_event;

  }

  return 0;

}

  

No caso de teste, empurrei objetos na fila my_event_queue três vezes no total e, em seguida, usei loops while e front para obter o endereço do objeto na fila e colocá-lo.

O problema está em excluir my_event. Em teoria, std::queue não é responsável pela destruição de objetos. Ou seja, seu novo objeto precisa ser excluído por si só, então eu o excluo toda vez que retiro um objeto.

Então o aborto ocorreu quando o loop while atingiu a segunda vez.Quando olhei para a memória, descobri que a memória durante a segunda exclusão não estava alocada, então o aborto foi acionado.

Como pode ser visto na captura de tela, o tamanho do identificador é de 4 bytes, o que significa que a área de memória está alocada na área marcada por três caixas vermelhas. De acordo com a suposição, cada exclusão deve apagar a área de memória de 4 bytes. Ou seja, apague o primeiro quadro vermelho pela primeira vez e apague o segundo quadro vermelho pela segunda vez.

Mas, na verdade, a primeira exclusão apagou 20 bytes do comprimento da memória, o que resultou na segunda exclusão acessando a memória não alocada.

Pesquisas subsequentes descobriram que isso ocorreu porque um valor foi passado em vez de um ponteiro ao empurrar, fazendo com que std::queue chamasse o construtor de cópia (se o construtor de cópia não for definido explicitamente, o padrão será chamado), então a fila é na verdade, uma cópia salva.

A cópia será ativamente destruída toda vez que aparecermos, e a ontologia não será afetada (precisamos excluí-la manualmente), então apenas pegamos o ponteiro da cópia e a excluímos após o pop. O endereço neste momento já está um ponteiro pendente. O comportamento é indefinido

Observe que 20 bytes é o tamanho padrão da fila

Como resolver isso?

Podemos declarar um array com antecedência, colocar o endereço após new nele e excluí-lo em sequência após o último uso.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

MyEvent* task[3];

for (int i = 0; i < 3; i++) {

  task[i] = new MyEvent();

  my_event_queue.push(*task[i]);

  auto task = new MyEvent();

  my_event_queue.push(*task);

}

...

// 此处只是方便测试

delete task[0];

delete task[1];

delete task[2];

 

当然更好的办法是使用智能指针来保证自动释放内存 std::queue<std::unique_ptr<MyEvent>> my_event_queue;

示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

#include <Windows.h>

#include <synchapi.h>

#include <iostream>

#include <memory>

#include <queue>

struct MyEvent {

  MyEvent() { event_ = CreateEvent(nullptr, 0, 0, 0); }

  // 添加移动构造函数

  MyEvent(MyEvent&& other) : event_(other.event_) { other.event_ = nullptr; }

  ~MyEvent() {

    if (event_ != nullptr) {

      CloseHandle(event_);  // 显式关闭句柄

    }

    std::cout << "MyEvent deconstruct" << std::endl;

  }

  void Run() {

    if (event_ != nullptr) {

      SetEvent(event_);

    }

  }

 private:

  HANDLE event_;

};

int main() {

  std::queue<std::unique_ptr<MyEvent>> my_event_queue;

  for (int i = 0; i < 3; i++) {

    auto task = std::make_unique<MyEvent>();

    my_event_queue.push(std::move(task));  // 使用 std::move 将对象放入队列

  }

  while (!my_event_queue.empty()) {

    auto& my_event = my_event_queue.front();

    my_event->Run();

    my_event_queue.pop();

  }

  return 0;

}

Acho que você gosta

Origin blog.csdn.net/2301_78834737/article/details/132004587
Recomendado
Clasificación