自定义Message结构体入队列后内存地址不一致及内存泄漏疑问
问题描述
初始代码与疑问
我正在创建自定义对象Message并将其加入队列,这会调用拷贝构造函数,但memmove()后的地址与预期不符。初始代码如下:
#include <iostream> #include <queue> struct Message { char* data = nullptr; size_t size = 0; Message() = default; ~Message() { delete[] data; } Message(const Message& other) { this->data = new char[other.size]; this->size = other.size; memmove(this->data, other.data, this->size); } }; std::queue<Message> message_queue; int main() { Message message; message.size = 50; message.data = new char[message.size]; std::cout << &message.data << std::endl; message_queue.push(std::move(message)); Message other_message = message_queue.front(); message_queue.pop(); std::cout << &other_message.data << std::endl; return 0; }
我疑惑为何将对象入队列后获取的内存地址不同,地址难道不该一致吗?另外如果不定义~Message()执行delete[] data;是否会造成内存泄漏?
编辑1
若不在析构函数中delete确实会内存泄漏,但为何内存位置不同?
编辑2
我更新了类代码如下:
#include <iostream> #include <queue> #include <cstring> #include <utility> struct Message { char* data = nullptr; size_t size = 0; Message(size_t in_size) { size = in_size; data = new char[size]; } ~Message() { delete[] data; } Message(const Message& other) { std::cout << "Message(const Message& other)\n"; this->size = other.size; this->data = new char[this->size]; memmove(this->data, other.data, this->size); } Message& operator=(const Message& other) { std::cout << "Message& operator=(const Message& other)\n"; if (this == &other) { return *this; } Message temp(other); std::swap(size, temp.size); std::swap(data, temp.data); return *this; } Message(Message&& other) noexcept : data(std::exchange(other.data, nullptr)) { std::cout << "Message(Message&& other) noexcept\n"; } Message& operator=(Message&& other) noexcept { std::cout << "Message& operator=(Message&& other) noexcept\n"; Message temp(std::move(other)); std::swap(data, temp.data); std::swap(size, temp.size); return *this; } }; std::queue<Message> message_queue; int main() { Message message(50); message_queue.push(message); Message other_message = message_queue.front(); message_queue.pop(); std::cout << (void*) message.data << "\n" << (void*) other_message.data << "\n"; return 0; }
但输出仍显示data指向不同地址:
Message(const Message& other) Message(const Message& other) 0x1b0c2a53400 0x1b0c2a52f40
我认为仍存在问题,但不确定问题所在。
问题解答
1. 为什么data的地址不同?
这是因为你实现的是深拷贝的拷贝构造函数:
- 在拷贝构造函数中,你明确调用了
new char[this->size],为新对象的data分配了一块全新的内存空间,再用memmove把原对象的数据复制过去。 - 每次调用拷贝构造函数(比如
push入队列、front赋值给other_message时),都会创建一个独立的Message对象,每个对象的data都指向专属的堆内存块,地址自然不同。
如果你期望地址一致,那是浅拷贝的行为,但浅拷贝会导致多个对象共享同一块内存,析构时会重复释放同一块内存,引发未定义行为——你的深拷贝实现其实是正确的,刚好避免了这个风险。
另外注意初始代码中你打印的是&message.data:这是成员变量data本身的地址(也就是对象里存储指针的那个内存位置),每个Message对象是独立的,所以它们的成员变量data的地址肯定不同;编辑2中你打印的是data指向的堆内存地址,因为深拷贝分配了新内存,地址也会不同。
2. 关于内存泄漏
如果不定义析构函数,编译器会生成默认析构函数,但默认析构函数不会自动delete[] data——因为data是原始指针,编译器不知道它指向堆内存。当Message对象被销毁时,data指向的堆内存不会被释放,必然造成内存泄漏。你当前定义的析构函数是正确的,能避免这个问题。
3. 编辑2中的拷贝调用说明
输出里的两次Message(const Message& other)分别来自:
- 第一次:
message_queue.push(message)——把栈上的message拷贝到队列的内存空间中; - 第二次:
Message other_message = message_queue.front()——把队列中的对象拷贝到栈上的other_message中。
如果想减少拷贝,可以改用std::move触发移动构造:
// 入队时用move转移所有权 message_queue.push(std::move(message)); // 取出时用move转移所有权 Message other_message = std::move(message_queue.front());
这样会调用移动构造函数,把原对象的data所有权转移给新对象,此时转移前后的data会指向同一块内存,但原对象的data会被置为nullptr,避免重复释放。
内容的提问来源于stack exchange,提问作者Zuiax

