Неверно проиндексированные массивы char [] в Visual Studio 2005 - PullRequest
2 голосов
/ 11 марта 2009

Я затрудняюсь объяснить, что, по-видимому, неправильно адресуется в массивах char[] класса C ++, который я унаследовал. Я использую Visual Studio 2005 и сузил проблему до этого:

MyClass.h:

class MyClass {
public:
  MyClass();
  virtual ~MyClass();
  void Reset();
  // More member functions. . .

  char m_szString[128];
  // More member variables. . .
}

MyClass.cpp:

MyClass::MyClass() { Reset(); }
MyClass::Reset()   { m_szString[0] = 'X'; }
// . . .

В процессе пошагового выполнения программы я обнаружил, что функция Reset() фактически устанавливает m_szString[4] на 'X' & mdash; не m_szString[0], как я ожидал. Согласно окну наблюдения, единственным элементом в классе до m_szString[] является указатель на vftable, __vfptr, который составляет 4 байта.

Если я добавлю больше переменных-членов к MyClass, последующие строки будут неправильно адресованы различными, постоянно увеличивающимися кратными 4 байтами. Не просто выровненные с 4-байтовыми границами, но на самом деле смещение на кратные 4. Это как если бы компилятор пропускал вдвое необходимое пространство для каждой vftable ... но это чисто думаю.

Сообщалось о некоторых похожих проблемах (Google, MSDN), но я не нашел ответов.


Дополнительная информация / Частичное решение: Этот класс является единственной переменной-членом класса-оболочки, который становится DLL. Родитель был первоначально объявлен как

class ATL_NO_VTABLE CWrapperClass

Удаление ATL_NO_VTABLE исправило проблему с выравниванием.

Я все еще вижу переполнения буфера, но их довольно легко отследить.

Можете ли вы объяснить ATL_NO_VTABLE в терминах, понятных для разработчика встроенного C, который имел очень ограниченный опыт работы с COM, помимо BSTR, или, что еще лучше, предоставить указатель (извините) на хорошую ссылку?


Еще больше: Этот вопрос предоставляет полезную информацию об отладке.

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

Ответы [ 4 ]

1 голос
/ 11 марта 2009

Моим первым предположением будет компиляция с включенной непреднамеренной оптимизацией.

Мое второе предположение: что-то странное происходит с поддержкой юникода (например, char против wchar_t).

1 голос
/ 11 марта 2009

Два вопроса здесь:

  • В чем проблема? Я не имею в виду то, что кажется отрадным в отладчике, но что на самом деле не так на верхнем уровне? То, что вы видите в отладчике, может быть результатом оптимизации, а не фактической ошибкой.
  • У вас есть минимальный код, который воспроизводит ошибку? Вышесказанное мало что говорит мне. Если бы я столкнулся с точно той же проблемой ранее, это могло бы быть полезно, но у меня нет. Так что, если я хотел воспроизвести проблему, что мне делать?
0 голосов
/ 31 марта 2009

Ответ

Компилятор фактически вычислял неправильный адрес для некоторых членов класса. Виновной оказалась директива #pragma pack 1, скрытая в непонятном заголовочном файле, который был #included в некоторых исходных файлах.

Как я нашел

50% настойчивости, 50% удачи и 10% математических навыков. : -)

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

typedef int BOOL;  // Legacy, makes my skin crawl; don't ask.
BOOL isConnected() { return m_bConnected; };
BOOL m_bConnected;

... и функция вернула false, когда я знал, что m_bConnected был true:

  • Окно просмотра показало правильное значение. Программные изменения в m_bConnected были отражены в окне.
  • Часы на &m_bConnected показали, что они начали 8 байтов в классе.
  • Окно сырой памяти показало правильное значение. Программные изменения в m_bConnected отразились и там.
  • Все 4 байта до m_bConnected были 0, что я интерпретировал как заполнение.
  • Шаги отладчика по коду ясно показывают возвращаемое значение false!

Итак, я проверил окно разборки и нашел это (мои комментарии):

mov    eax,dword ptr [this]        ; address of the class instance
mov    eax,dword ptr [eax+4]       ; offset to m_bConnected

Другими словами, компилятор вычислил смещение как 4, когда оно должно было быть 8.

Aha!

Интересно, что когда я удалил определение isConnected() из заголовка и поместил его в исходный файл, адрес был вычислен правильно ! Это убедило меня, что это действительно была проблема с упаковкой, и поиск в базе кода для #pragma pack быстро выявил виновника, древний заголовочный файл, который (законно) требовал выравнивания на границах в 1 байт, но никогда не сбрасывал упаковку по умолчанию.

Исправление

Решение было так же просто, как заключить в заголовок заголовок, как это:

#pragma pack(push)
// original code here
#pragma pack(pop)

Спасибо за ваш интерес. И Сара, если ты читаешь, я собираюсь потанцевать на своем столе !

0 голосов
/ 12 марта 2009

Всегда лучше завершить строку нулем, прежде чем начать использовать. Таким образом, даже во время отладки вы получите более четкое представление о массиве. Более того, вы объявили m_szString публично. Лучше объявить массив в приватной области и вернуть его с помощью функции-члена

...