Преобразованный код 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[]? Только то, что вы не видите уменьшения в отладчике, не означает, что его там нет. Проверьте, доступен ли нужный элемент, записав в него отличительное значение.

Другие вопросы по тегам