TL;DR: когда я запускаю свою программу на C++ на Mac под OS X Yosemite, указатель повреждается при возврате функции. Как мне предотвратить это? (и почему?)
В этом примере программы у меня есть структура данных типа category_map<T>
, которая фактически представляет собой просто
map<string, list<pair<string, T> > >
Класс category_map
имеет несколько методов, в том числе get(string& name)
, который извлекает list
, хранящийся под заданным name
, и возвращает T
из первого элемента этого списка. В моем случае T
— это тип указателя. Указатель, который код извлекает из первого pair
в list
— это будет p
в приведенном ниже листинге кода — действителен. Сеанс отладчика показывает, что значение p
в последней строке функции — закрывающая фигурная скобка перед запуском деструкторов — является допустимой ячейкой памяти, например, 0x100809c00
.
T& get(const string& name) const {
cerr << "searching for " << name << endl;
typename super::const_iterator map_iterator = super::find(name);
// the real code doesn't assume it will be found
list_type the_list = map_iterator->second;
T& p = the_list.front().second;
cerr << "found " << val_loc_string<T>(p) << endl;
return p;
}
Однако, когда я компилирую и запускаю код на Mac (OS X Yosemite), а не на Linux, где-то в процессе очистки от этой функции что-то пишет в то же место в памяти, так что возвращаемый указатель - сохраняется в переменной ip
в следующем листинге кода ниже — поврежден. Например, это может быть 0x3000100809c00
или 0x5000100809c00
. Поврежденный указатель всегда является исходным указателем с одним или несколькими дополнительными битами, установленными во втором старшем байте 8-байтового адреса.
int main(const int argc, const char** argv) {
category_map<int*> imap;
int a;
imap.add("Q1", "m", &a);
imap.add("Q1", "r", &a);
imap.add("Q2", "m", &a);
int* ip = imap.get("Q1");
cerr << "return value: " << val_loc_string<int*>(ip) << endl;
cout << *ip << endl;
}
Используя GDB (установленный через MacPorts), я определил конкретную инструкцию, которая записывает дополнительные биты в ячейку памяти.
0x00007fff93188279: cmp $0x2,%eax
0x00007fff9318827c: jb 0x7fff9318828d
0x00007fff9318827e: shl $0x4,%rax
=> 0x00007fff93188282: mov %r10w,-0x2(%rax,%rdx,1)
0x00007fff93188288: mov %r10w,0x10(%rdx)
0x00007fff9318828d: test %r10w,%r10w
0x00007fff93188291: jne 0x7fff93188299
(дополнительный контекст), но это не очень помогает, потому что это не часть функция C/C++, я недостаточно хорошо разбираюсь в ассемблере, чтобы понять, что она делает в больших масштабах, и " rel="nofollow">backtrace - это мусор, поэтому я не могу поместить код в контекст. (Я также зафиксировал значения регистров непосредственно перед инструкция, которая повреждает указатель, если это по какой-то причине помогает.)
Поскольку я создаю экземпляр category_map<T>
только с типами указателей, я мог изменить возвращаемый тип get
на T
(вместо T&
), и это действительно решает (или, по крайней мере, обходит) проблему. Но это делает структуру данных более полезной, если она может хранить большие объекты и возвращать их по ссылке, и я думаю, что это должно быть возможно. Кроме того, какую бы ошибку я ни сделал при кодировании, я хотел бы понять, чтобы не повторять ее снова. Может ли кто-нибудь указать, что я сделал неправильно, и правильный способ исправить это без изменения API?