C ++ вызывает совершенно неправильный (виртуальный) метод объекта

У меня есть код C ++ (написанный кем-то другим), который, похоже, вызывает неправильную функцию. Вот ситуация:

UTF8InputStreamFromBuffer* cstream = foo();
wstring fn = L"foo";
DocumentReader* reader;

if (a_condition_true_for_some_files_false_for_others) {
    reader = (DocumentReader*) _new GoodDocumentReader();
} else {
    reader = (DocumentReader*) _new BadDocumentReader();
}

// the crash happens inside the following call
// when a BadDocumentReader is used
doc = reader->readDocument(*cstream, fn);

Файлы, для которых выполняется условие, обрабатываются нормально; те, для которых это ложный сбой. Иерархия классов для DocumentReader выглядит следующим образом:

class GenericDocumentReader {
    virtual Document* readDocument(InputStream &strm, const wchar_t * filename) = 0;
}

class DocumentReader : public GenericDocumentReader {
    virtual Document* readDocument(InputStream &strm, const wchar_t * filename) {
        // some stuff
    }
};

class GoodDocumentReader : public DocumentReader {
    Document* readDocument(InputStream & strm, const wchar_t * filename);
}

class BadDocumentReader : public DocumentReader {
    virtual Document* readDocument(InputStream &stream, const wchar_t * filename);
    virtual Document* readDocument(const LocatedString *source, const wchar_t * filename);
    virtual Document* readDocument(const LocatedString *source, const wchar_t * filename, Symbol inputType);
}

Следующее также имеет значение:

class UTF8InputStreamFromBuffer : public wistringstream {
    // foo
};
typedef std::basic_istream<wchar_t> InputStream;

Запуск в отладчике Visual C ++ показывает, что вызов readDocument в BadDocumentReader вызывает не

readDocument(InputStream&, const wchar_t*)

, а скорее

readDocument(const LocatedString* source, const wchar_t *, Symbol)

. подтверждается наклеиванием инструкций cout во все прочитанные документы. После вызова исходный аргумент, конечно, заполнен мусором, который вскоре вызывает сбой. У LocationString есть неявный конструктор с одним аргументом из InputStream, но проверка с помощью cout показывает, что он не вызывается. Есть идеи, чем это объяснить?

Edit : другие, возможно, важные детали: классы DocumentReader находятся в другой библиотеке, чем вызывающий код. Я также полностью перестроил весь код, и проблема осталась.

Редактировать 2 : Я использую Visual C ++ 2008.

Редактировать 3 : Я попытался сделать "минимально компилируемый пример" "с тем же поведением, но не смог воспроизвести проблему.

Edit 4 :

По предложению Билли ONeal я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мое подозрение, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : Классы DocumentReader находятся в другой библиотеке, чем вызывающий код. Я также полностью перестроил весь код, и проблема осталась.

Редактировать 2 : Я использую Visual C ++ 2008.

Редактировать 3 : Я попытался сделать "минимально компилируемый пример" "с тем же поведением, но не смог воспроизвести проблему.

Редактировать 4 :

По предложению Билли Онила я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мое подозрение, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : Классы DocumentReader находятся в другой библиотеке, чем вызывающий код. Я также полностью перестроил весь код, и проблема осталась.

Редактировать 2 : Я использую Visual C ++ 2008.

Редактировать 3 : Я попытался сделать "минимально компилируемый пример" "с тем же поведением, но не смог воспроизвести проблему.

Редактировать 4 :

По предложению Билли Онила я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мои подозрения, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : Я также полностью перестроил весь код, и проблема осталась.

Редактировать 2 : Я использую Visual C ++ 2008.

Редактировать 3 : Я попытался сделать "минимально компилируемый пример" "с тем же поведением, но не смог воспроизвести проблему.

Редактировать 4 :

По предложению Билли Онила я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мое подозрение, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : Я также полностью перестроил весь код, и проблема осталась.

Редактировать 2 : Я использую Visual C ++ 2008.

Редактировать 3 : Я попытался сделать "минимально компилируемый пример" "с тем же поведением, но не смог воспроизвести проблему.

Редактировать 4 :

По предложению Билли Онила я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мои подозрения, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : с тем же поведением, но не смог воспроизвести проблему.

Edit 4 :

По предложению Билли ONeal я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мое подозрение, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : с тем же поведением, но не смог воспроизвести проблему.

Edit 4 :

По предложению Билли ONeal я попытался изменить порядок методов readDocument в заголовке BadDocumentReader. Конечно, когда я меняю порядок, он меняет вызываемую функцию. Мне кажется, это подтверждает мое подозрение, что с индексацией в vtable происходит что-то странное, но я не уверен, что это вызывает.

Редактировать 5 : Вот разборка нескольких строк перед вызовом функции:

00559728  mov         edx,dword ptr [reader] 
0055972E  mov         eax,dword ptr [edx] 
00559730  mov         ecx,dword ptr [reader] 
00559736  mov         edx,dword ptr [eax] 
00559738  call        edx  

Я не очень разбираюсь в сборке, но мне кажется, что это разыменование указателя переменной считывателя. Первым, что хранится в этой части памяти, должен быть указатель на vtable, поэтому он разыменовывает его в eax. Затем он помещает первый элемент в vtable в edx и вызывает его. Перекомпиляция с разными порядками методов, похоже, этого не меняет. Он всегда хочет вызвать первое, что есть в vtable. (Я мог совершенно неправильно это понять, вообще не имея представления о сборке ...)

Спасибо за вашу помощь.

Редактировать 6: Я обнаружил проблему и прошу прощения за зря потраченное время. Проблема заключалась в том, что GoodDocumentReader должен был быть объявлен как подкласс DocumentReader, но на самом деле это не так. Приведение в стиле C подавляло ошибку компилятора (должен был прислушаться к вам, @sellibitze, если вы хотите отправить свой комментарий в качестве ответа, я отмечу его как правильный). Сложность заключается в том, что код работал несколько месяцев по чистой случайности, пока не появилась ревизия, когда кто-то добавил еще две виртуальные функции в GoodDocumentReader, так что он больше не вызывал нужную функцию по счастливой случайности.

15
задан Ryan Gabbard 26 January 2011 в 16:29
поделиться