Я недавно столкнулся с неприятным schrödinbug. При попытке загрузить файл в плоское представление памяти автор написал такой код:
class Line final { public:
int stuff[3];
char* data;
}
//...
Line* line = /*...*/;
//Trying to treat line->data like an array. This is *wrong*.
line->data = reinterpret_cast<char*>(line) + 3*sizeof(int);
//...
line->data[0] = /*...*/
line->data[1] = /*...*/
//...
line->data[n] = /*...*/ //"line->data" changes because of this line!
Итак, происходит то, что первые строки кода по существу установлены line->data
равно &line->data
, Это ошибка, потому что любые изменения в значениях, на которые указывает line->data
также может изменить то, что line->data
сама указывает на!
Тогда мне показалось любопытным, что проблема возникла так долго. Насколько я понимаю, если не квалифицирован с restrict
(или для g ++ / MSVC __restrict
) компилятор должен предполагать, что указатели являются псевдонимами. Так что, если я установлю line->data[0]
быть чем-то, то это будет видно для следующего доступа, line->data[1]
и почти наверняка будет недействительным. Однако в отладчике изменения не были видны намного позже, и записи продолжались долго и счастливо.
Я предполагаю, что компилятор (в данном случае MSVC 2013) не считал возможным использование псевдонимов. Это разрешено?
Насколько я понимаю, что, если только он не квалифицирован как restrict (или для g ++ / MSVC __restrict), компилятор должен предполагать, что указатели являются псевдонимами.
Это неверно Компилятору разрешается предполагать, что указатели указывают только псевдонимы, указывающие на один и тот же тип, или указатели на char
,
class X;
class Y;
X *ptr_x = ...;
Y *ptr_y = ...;
char *ptr_char = ...;
Здесь компилятор может предположить, что ptr_x
не псевдоним ptr_y
, Он не может делать предположения о ptr_char
, тем не мение.
Трудно точно понять, в чем проблема. Я уже давно решил эту проблему, и сейчас я готовлю несколько проектов. Оглядываясь назад, кажется, что комментарии к исходному вопросу были наиболее успешными в предоставлении подсказок для объяснения поведения:
Возможно из-за заполнения, в зависимости от разрядности системы.
а также:
Ну, одна вещь, которая сразу приходит на ум, это выравнивание. Это работает на 64-битной платформе? Если это так, вычисление в арифметике указателя не учитывает отступы.
На 64-битной архитектуре, которая действительно была скомпилирована, я предполагаю, что класс в исходном вопросе будет размещен в памяти следующим образом (типы, скорректированные для ясности):
int32_t stuff_0;
int32_t stuff_1;
int32_t stuff_2;
//4 bytes of empty space
char* data;
Заполнение происходит потому, что char*
указатель должен быть 8
-байт выровнен. С первых трех int
взять 3*32/8=96/8=12
байт, чтобы получить это выравнивание, компилятор должен вставить дополнительный 4
байты, чтобы привести накладные расходы на раунд 16
байт.
когда data
инициализирован, он был неправильно инициализирован, чтобы указывать на начало пустого пространства. Итак, пишет data[n]
, 0<=n<4
ударил обивка. Это только при доступе data[4]
что у нас проблема.
Я говорю «самый успешный» с тех пор, пока вопрос делал в основном происходят около пятого доступа, к моей памяти иногда возникала проблема, даже при отладке. И, как я уже писал, это было schrödinbug (то есть ошибка, которая должна была произойти, но не произошла — и теперь, когда она наблюдалась, всегда есть). У меня нет данных о предыдущем типе выполняемых данных, но возможно, что логика заставила критический диапазон указателя не быть затронутым.