Как отловить неопределенное поведение при инициализации аргумента функции - PullRequest
0 голосов
/ 14 декабря 2018

Следующий код работает в clang ++, но эффектно вылетает в g ​​++

#include<vector>
#include<iostream>

template<class Iterator>
double abs_sum(double current_sum, Iterator it, Iterator it_end){
    if (it == it_end)
        return current_sum;
    return abs_sum(current_sum+std::abs(*it),++it,it_end);
}


int main(int argc, char** argv){
    std::vector<double> values {1.0, 2.0,-5};

    std::cout << abs_sum(0.0,values.begin(),values.end()) << std::endl;;
}

Виновной оказалась эта строка:

return abs_sum(current_sum+std::abs(*it),++it,it_end);

в clang, *it оцениваетсядо ++it, в g ++ все наоборот, итератор должен быть увеличен до разыменования.Оказывается, порядок, в котором оцениваются аргументы функции, определяется реализацией.

Мой вопрос: как я могу отследить этот тип ошибки?В идеале я хочу иметь ошибку или, по крайней мере, предупреждение, когда случайно завису от деталей, специфичных для реализации.

Ни clang, ни gcc не выдают никаких предупреждений, даже с -Wall.

Ответы [ 3 ]

0 голосов
/ 14 декабря 2018

То, что у вас изначально есть не неопределенное поведение, а неопределенное поведение.Компилятору не требуется для выдачи какой-либо диагностики для неопределенного поведения.

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

Но результат этого неопределенного поведения в этом случае приводит к разыменованию конечного итератора, что, в свою очередь,приводит к неопределенному поведению.


GCC и Clang не имеют никакой общей опции компилятора для выдачи диагностики для неопределенного поведения.

В GCC есть опция fstrong-eval-order, который выполняет следующее:

Оценивает доступ к элементам, подписку массива и выражения сдвига в порядке слева направо и оценивает присваивание в порядке справа налево, как принято для C ++17.По умолчанию включено с -std=c++17.-fstrong-eval-order=some включает только порядок доступа к элементам и выражения сдвига и является значением по умолчанию без -std=c++17.

Существует также опция -Wreorder (только для C ++ и Objective-C ++), которая выполняетthis:

Предупреждать, когда порядок инициализаторов элементов, указанный в коде, не соответствует порядку, в котором они должны быть выполнены

Но я не думаю, что эти параметры будутбыть полезным в вашем конкретном случае.

Так что в этом конкретном случае вы можете выполнять операции в намеченном порядке.

0 голосов
/ 14 декабря 2018

К сожалению, даже с -Wextra (помните, -Wall больше похоже на -Wsome и, следовательно, недостаточно), здесь нет предупреждения, что немного разочаровывает.

В более тривиальномслучай с примитивом, где раса * более очевидна для компилятора:

void foo(int, int) {}

int main()
{
    int x = 42;
    foo(++x, x);
}

… вы предупреждены :

main.cpp: In function 'int main()':
main.cpp:6:9: warning: operation on 'x' may be undefined [-Wsequence-point]
     foo(++x, x);
         ^~~
main.cpp:6:9: warning: operation on 'x' may be undefined [-Wsequence-point]

(* не настоящая гонка, но вы понимаете, о чем я)

Но компилятору слишком сложно "знать", что ваши операции над итератором - чтение и запись соответственно.

В конечном счете, я боюсь, что вам придется полагаться на тестирование, остроумие и догадки.:)

0 голосов
/ 14 декабря 2018

Мой вопрос: как я могу обнаружить этот тип ошибки?

Вы этого не делаете.Неопределенное поведение не определено.Вы не можете его поймать ...

... но вам могут помочь некоторые инструменты:

  • ваш компилятор: все предупреждения включены (g ++ / clang ++ -Wall -Wextra -pedantic - хорошее начало);
  • cppcheck;
  • clang-analyizer;

Хотя они не дают никаких гарантий.Вот почему C ++ hard .Вы должны (вы, кодер) знать лучше и не писать UB.Удачи.

...