Указатель поврежден при возврате из функции

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?


person David Z    schedule 24.03.2015    source источник


Ответы (1)


С участием

list_type the_list = map_iterator->second;

вы делаете копию map_iterator->second. the_list — это локальный объект функции. потом

T& p = the_list.front().second;
return p;

возвращает ссылку на что-то, что живет до тех пор, пока этот локальный объект функции, и уничтожается, когда функция выходит. Ссылка болтается.

Мне кажется, что вы не собирались копировать список, так что

//          +------ const because get() is const-qualified
//          v   v-- reference
list_type const &the_list = map_iterator->second;

//  v-- const because the_list is const
T const& p = the_list.front().second;

должен исправить это, если вы можете заставить get() const возвращать T const &1. В противном случае у вас возникнет проблема с попыткой вернуть ссылку на неконстантный элемент из функции-члена const; это нарушило бы константную правильность и поэтому запрещено (если бы это было разрешено, вы могли бы изменять константные объекты через эту ссылку).

1 Вы также можете заставить get const() возвращать значение, а не ссылку, но, похоже, нет причин для принудительного копирования этой копии.

person Wintermute    schedule 24.03.2015