C++ std::list对象二进制存读问题及编译错误排查
问题背景
尝试将存储自定义Order类对象的std::list写入二进制文件并读取,最初直接读写std::list的内存数据,加载后遍历列表触发segmentation fault(段错误);改为逐字段序列化后,编译时出现std::string.data()无法转换为char*的错误。
原始Order类代码
class Order{ private: string name; string address; int number_of_tickets; public: bool operator == (const Order& o) const { return name == o.getName() && address == o.getAddress() && number_of_tickets == o.getTickets(); } bool operator != (const Order& o) const { return !operator==(o); } Order(string n, string a, int num){ this->name = n; this->address = a; this->number_of_tickets = num; } Order copy() const{ return Order(this->name, this->address, this->number_of_tickets); } void display() const{ cout << getName() << " " << getAddress() << " " << getTickets() << endl; } string getName() const{ return name; } string getAddress() const{ return address; } int getTickets() const{ return number_of_tickets; } };
最初的实现与运行错误
直接读写std::list内存的save/load函数:
void save(list<Order>& l){ fstream save; save.open("orders.list", ios::out | ios::binary); long size = sizeof(l); save.write(reinterpret_cast<char*>(&size), sizeof(size)); cout << endl << "Size is = " << size << endl; save.write(reinterpret_cast<char*>(&l), size); cout << "Orders Saved!\n"; } void load(list<Order>& l){ fstream load; load.open("orders.list", ios::in | ios::binary); long size; load.read(reinterpret_cast<char*>(&size), sizeof(size)); cout << endl << "Size is = " << size << endl; load.read(reinterpret_cast<char*>(&l), size); cout << "Orders Loaded!\n"; }
错误表现
程序运行时可正常显示列表,但加载保存的数据后调用displayAll遍历列表时触发segmentation fault。
修改后的代码与编译错误
改为逐字段序列化的save/load函数:
void save(list<Order>& l){ fstream save; save.open("orders.list", ios::out | ios::binary); int size = l.size(); save.write(reinterpret_cast<char*>(&size), sizeof(size)); for(const auto& order : l){ string name = order.getName(); string::size_type size_name = name.length(); save.write(reinterpret_cast<char*>(&size_name), sizeof(size_name)); save.write(name.data(), size_name); string address = order.getAddress(); string::size_type size_address = address.length(); save.write(reinterpret_cast<char*>(&size_address), sizeof(size_address)); save.write(address.data(), size_address); int tickets = order.getTickets(); save.write(reinterpret_cast<char*>(&tickets), sizeof(tickets)); } } void load(list<Order>& l){ ifstream load; load.open("orders.list", ios::binary); int size; load.read(reinterpret_cast<char*>(&size), sizeof(size)); for(int i = 0; i < size; i++){ string name; string::size_type size_name; load.read(reinterpret_cast<char*>(&size_name), sizeof(size_name)); name.resize(size_name); load.read(name.data(), size_name); string address; string::size_type size_address; load.read(reinterpret_cast<char*>(&size_address), sizeof(size_address)); address.resize(size_address); load.read(address.data(), size_address); int tickets; load.read(reinterpret_cast<char*>(&tickets), sizeof(tickets)); l.push_back(Order(name, address, tickets)); } }
编译错误信息
mechy88@LAPTOP-SJIPE7SP:/mnt/c/Users/Miguel/Desktop/ADNU-CS-repo/CSDC102_E3$ g++ PAJARILLO_E3.cpp PAJARILLO_E3.cpp: In function ‘void load(std::__cxx11::list&)’: PAJARILLO_E3.cpp:215:28: error: invalid conversion from ‘const char*’ to ‘std::basic_istream::char_type*’ {aka ‘char*’} [-fpermissive] 215 | load.read(name.data(), size_name); | ~~~~~~~~~^~ | | | const char* In file included from /usr/include/c++/9/iostream:40, from PAJARILLO_E3.cpp:1: /usr/include/c++/9/istream:486:23: note: initializing argument 1 of ‘std::basic_istream<_CharT, _Traits>& std::basic_istream<_CharT, _Traits>::read(std::basic_istream<_CharT, _Traits>::char_type*, std::streamsize) [with _CharT = char; _Traits = std::char_traits; std::basic_istream<_CharT, _Traits>::char_type = char; std::streamsize = long int]’ 486 | read(char_type* __s, streamsize __n); | ~~~~~~~~~~~^~~ PAJARILLO_E3.cpp:221:31: error: invalid conversion from ‘const char*’ to ‘std::basic_istream::char_type*’ {aka ‘char*’} [-fpermissive] 221 | load.read(address.data(), size_address); | ~~~~~~~~~~~~^~ | | | const char* In file included from /usr/include/c++/9/iostream:40, from PAJARILLO_E3.cpp:1: /usr/include/c++/9/istream:486:23: note: initializing argument 1 of ‘std::basic_istream<_CharT, _Traits>& std::basic_istream<_CharT, _Traits>::read(std::basic_istream<_CharT, _Traits>::char_type*, std::streamsize) [with _CharT = char; _Traits = std::char_traits; std::basic_istream<_CharT, _Traits>::char_type = char; std::streamsize = long int]’ 486 | read(char_type* __s, streamsize __n); | ~~~~~~~~~~~^~~
解决方案
1. 最初段错误的原因
std::list是链表容器,内部存储的是节点指针、链表头尾指针等结构,直接读写容器的内存数据,保存的是当前进程中的内存地址。加载到新进程后,这些地址已经无效,访问时就会触发segmentation fault。正确的做法是逐对象、逐字段进行序列化/反序列化。
2. 编译错误的解决
C++17之前,std::string::data()返回的是const char*,而istream::read()要求传入可写的char*。有两种安全的解决方式:
方式一:使用const_cast转换
因为已经通过name.resize(size_name)为字符串分配了足够的可写内存,这里用const_cast去除const属性是安全的:
load.read(const_cast<char*>(name.data()), size_name); load.read(const_cast<char*>(address.data()), size_address);
方式二:取字符串首元素的地址
字符串resize后,内部的字符数组是连续且可写的,直接取首元素的地址即可得到char*:
load.read(&name[0], size_name); load.read(&address[0], size_address);
方式三:升级到C++17及以上
C++17为std::string::data()提供了非const重载,直接调用即可返回char*,但需要确保编译器支持(编译时加-std=c++17参数)。
3. 额外优化建议
- 检查文件状态:打开文件后要检查是否成功,避免后续操作无意义执行:
if (!save.is_open()) { cerr << "Failed to open save file\n"; return; } - 检查流状态:读取数据后检查流是否正常,避免因文件损坏或读取不完整导致错误:
if (!load.read(reinterpret_cast<char*>(&size_name), sizeof(size_name))) { cerr << "Failed to read name length\n"; break; } - 封装序列化逻辑:把序列化/反序列化代码封装到
Order类的成员函数中,代码更整洁:class Order { // ... 现有代码 ... void serialize(ostream& os) const { string::size_type name_len = name.length(); os.write(reinterpret_cast<const char*>(&name_len), sizeof(name_len)); os.write(name.data(), name_len); string::size_type addr_len = address.length(); os.write(reinterpret_cast<const char*>(&addr_len), sizeof(addr_len)); os.write(address.data(), addr_len); os.write(reinterpret_cast<const char*>(&number_of_tickets), sizeof(number_of_tickets)); } void deserialize(istream& is) { string::size_type name_len; is.read(reinterpret_cast<char*>(&name_len), sizeof(name_len)); name.resize(name_len); is.read(const_cast<char*>(name.data()), name_len); string::size_type addr_len; is.read(reinterpret_cast<char*>(&addr_len), sizeof(addr_len)); address.resize(addr_len); is.read(const_cast<char*>(address.data()), addr_len); is.read(reinterpret_cast<char*>(&number_of_tickets), sizeof(number_of_tickets)); } }; - 字节序处理:如果需要跨平台序列化,要统一字节序(比如转成大端),避免不同字节序的平台读取数据错误。
内容的提问来源于stack exchange,提问作者Miguel Andre Repatacodo

