Преобразованный код F2C прерывается при оптимизации компилятором C++
У меня есть программа на C++ с методом, который выглядит примерно так:
int myMethod(int* arr1, int* arr2, int* index)
{
arr1--;
arr2--;
int val = arr1[*index];
int val2 = arr2[val];
return doMoreThings(val);
}
При включенной оптимизации (/O2) первая строка, в которой уменьшается первый указатель, не выполняется. Я отлаживаю оптимизированные и неоптимизированные сборки бок о бок и оптимизированные шаги сборки по уменьшению, в то время как неоптимизированная программа выполняет его. Это приводит к заметной разнице в поведении, когда он позже обращается к массиву с помощью arr[*index].
ОБНОВИТЬ
Как указал @stefaanv, декремент действительно может пропустить декремент, если вместо этого он перейдет в декрементированный индекс доступа, что, по-видимому, и происходит. Таким образом, пропущенный декремент не является причиной различий в поведении. Вместо этого есть что-то в использовании матриц, которое вызывает это.
Глядя дальше, я сузил его до метода, который содержит вложенные циклы, выполняющие умножение матриц. Часть метода выглядит следующим образом: задействованы 3 массива: a, wa и t. В начале метода транслятор f2c использует декремент, так что массив, который был 6 на 6 в Фортране, является плоским double[36]
в с. Но чтобы иметь возможность использовать старое индексирование, оно перемещает указатели массива назад на количество столбцов в матрице.
Обычно в этой переведенной программе f2c плоские массивы передаются как &someArray[1]
и методы начинаются с уменьшения каждого массива на единицу. @Christoph указал, что это должно быть допустимо, так как массив никогда не уменьшается за пределы объявленного диапазона.
В случае этого метода переданные массивы НЕ передаются как указатель на элемент дальше в массив &someArray[1]
но здесь массивы - это локальные статические массивы, объявленные с фиксированным размером, например mat[36]
и передается непосредственно в метод умножения.
void test()
{
double mat[36];
...
mul(mat, .., ..)
}
void mul(double* a, double* t, double*wa, int M, int N, int K)
{
// F2C array decrements.
a -= (1+M); // E.g. decrement by seven for a[6x6]!
t -= (1+N);
wa--;
...
for (j = K; j <= M; ++j) {
for (i = 1; i <= N; ++i) {
ii = K;
wa[i] = 0.;
for (p = 1; p <= N; ++p) {
wa[i] += t[p + i * t_dim1] * a[ii + j * a_dim1];
++ii;
}
}
ii = K;
for (i = 1; i <= N; ++i) {
a[ii + j * a_dim1] = wa[i];
if (j > kn) {
a[j + ii * a_dim1] = wa[i];
}
++ii;
}
}
}
Итак, вопрос:
Означает ли это, что поведение не определено и может нарушаться при оптимизации, когда вы делаете то, что f2c сделал здесь, то есть вычитаете 7 из двойного [36] указателя массива, но затем получаете доступ ко всем элементам в массиве в правильных местах (смещение 7)?
Изменить: нашел это в C FAQ, это применимо здесь?
Арифметика указателя определяется только до тех пор, пока указатель указывает в пределах одного и того же выделенного блока памяти или на воображаемый "завершающий" элемент, находящийся за ним; в противном случае поведение не определено, даже если указатель не разыменован..... Ссылки: K&R2 Sec. 5.3 с. 100, с. 5.4 с. 102-3, гл. А7.7, стр. 205-6; ISO Sec. 6.3.6; Обоснование с. 3.2.2.3.
ОБНОВЛЕНИЕ 2:
Если я перекомпилирую с многомерными массивами, используя уменьшенные индексы, а не уменьшенные указатели,
#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1 - 1 - a_dim1]
a_ref(1,2);
Затем метод выдает одинаковый (ожидаемый) результат независимо от оптимизаций. Кажется, что одномерные массивы, которые уменьшаются только на единицу, не создают никаких проблем.
Я мог бы изменить все многомерные массивы в программе, используя вышеуказанный метод доступа, но одиночных dim-массивов слишком много, чтобы их можно было изменить вручную, поэтому в идеале я хотел бы получить решение, которое работает для обоих.
Новые вопросы:
- Есть ли возможность для f2c использовать этот метод доступа к массиву, а не возиться с указателями? Похоже, это было бы простым изменением в f2c и созданием четко определенного кода, так что вы могли бы подумать, что это уже варианты.
- Существуют ли другие решения для этой проблемы (кроме пропуска оптимизаций и надежды, что программа хорошо себя ведет, несмотря на то, что полагается на неопределенное поведение).
- Что я могу сделать в компиляторе C++? Я компилирую с Microsoft C++ (2010), как управляемый проект C++.
2 ответа
Оптимизатор должен только следить за тем, чтобы не было заметного изменения поведения, поэтому он может выбрать не делать уменьшение и получать доступ к данным с уменьшенным индексом (дополнительное смещение может быть частью кода операции), поскольку функция использует копию указатель на массив. Вы не говорите нам, как на самом деле осуществляется доступ к массиву и действительно ли оптимизатор вносит ошибку, поэтому я могу только догадываться об этом.
Но, как уже сказал slartibartfast: это неопределенное поведение и декремент должен быть заменен на int val = arr1[*index-1];
после проверки этого *index > 0
Перемещение указателя на массив из указанного диапазона массивов и последующий доступ через него (хотя полное выражение возвращается в диапазон) является неопределенным поведением AFAIK. Тем не менее, почти во всех реализациях этот код должен работать так, как задумано, поэтому вопрос в том, на что вы смотрите? Может быть, в инструкции на ассемблере есть неявная предопределенность, берущая int из arr1[]? Только то, что вы не видите уменьшения в отладчике, не означает, что его там нет. Проверьте, доступен ли нужный элемент, записав в него отличительное значение.