Понимание примера heisenbug: различная точность регистров и основная память
Я прочитал wiki-страницу о heisenbug, но не понимаю этот пример. Может ли кто-нибудь объяснить это подробно?
Одним из распространенных примеров heisenbug является ошибка, возникающая при компиляции программы с оптимизирующим компилятором, но не тогда, когда одна и та же программа скомпилирована без оптимизации (как это часто делается с целью изучения ее с помощью отладчика). Во время отладки значения, которые оптимизированная программа обычно хранит в регистрах, часто переносятся в основную память. Это может повлиять, например, на результат сравнений с плавающей точкой, поскольку значение в памяти может иметь меньший диапазон и точность, чем значение в регистре.
Ответы
Ответ 1
Вот конкретный пример, недавно опубликованный:
Гейзенбаг с бесконечным циклом: выходит, если добавить распечатку
Это действительно хороший образец, потому что мы все можем воспроизвести его: http://ideone.com/rjY5kQ
Эти ошибки настолько зависят от очень точных характеристик платформы, что людям также очень трудно их воспроизвести.
В этом случае, когда "распечатка" не указана, программа выполняет сравнение с высокой точностью внутри регистров ЦП (выше, чем в double
). Но чтобы распечатать значение, компилятор решает переместить результат в основную память, что приводит к неявному усечению точности. Когда он использует это усеченное значение для сравнения, это успешно.
#include <iostream>
#include <cmath>
double up = 19.0 + (61.0/125.0);
double down = -32.0 - (2.0/3.0);
double rectangle = (up - down) * 8.0;
double f(double x) {
return (pow(x, 4.0)/500.0) - (pow(x, 2.0)/200.0) - 0.012;
}
double g(double x) {
return -(pow(x, 3.0)/30.0) + (x/20.0) + (1.0/6.0);
}
double area_upper(double x, double step) {
return (((up - f(x)) + (up - f(x + step))) * step) / 2.0;
}
double area_lower(double x, double step) {
return (((g(x) - down) + (g(x + step) - down)) * step) / 2.0;
}
double area(double x, double step) {
return area_upper(x, step) + area_lower(x, step);
}
int main() {
double current = 0, last = 0, step = 1.0;
do {
last = current;
step /= 10.0;
current = 0;
for(double x = 2.0; x < 10.0; x += step) current += area(x, step);
current = rectangle - current;
current = round(current * 1000.0) / 1000.0;
//std::cout << current << std::endl; //<-- COMMENT BACK IN TO "FIX" BUG
} while(current != last);
std::cout << current << std::endl;
return 0;
}
Редактировать: проверенная ошибка и исправить все еще экспонат: 20-Фев-17
Ответ 2
Это происходит из принципа неопределенности, который в основном гласит, что существует фундаментальный предел точности, с которой определенные пары физических свойств частицы могут быть известны одновременно. Если вы начнете наблюдать какую-то частицу слишком близко (т.е. вы точно знаете ее положение), то вы не сможете точно измерить ее импульс. (И если у вас есть точная скорость, то вы не можете сказать ее точное положение)
Итак, после этого Heisenbug - это ошибка, которая исчезает, когда вы внимательно смотрите.
В вашем примере, если вам нужно, чтобы программа работала хорошо, вы скомпилируете ее с оптимизацией и возникнет ошибка. Но как только вы войдете в режим отладки, вы не скомпилируете его с оптимизацией, которая устранит ошибку.
Поэтому, если вы начнете наблюдать за ошибкой слишком внимательно, вы не будете знать, каковы ее свойства (или не сможете ее найти), что напоминает принцип неопределенности Гейзенберга и поэтому называется Гейзенбаг.
Ответ 3
Идея заключается в том, что код скомпилирован в два состояния: один - нормальный или отладочный, а другой - оптимизированный или производственный.
Так же, как важно знать, что происходит с вопросом на квантовом уровне, мы также должны знать, что происходит с нашим кодом на уровне компилятора!