Почему программе C/C++ часто выключали оптимизацию в режиме отладки?

Исключение нулевого указателя - это индикатор того, что вы используете объект, не инициализируя его.

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

public class Student {

    private int id;

    public int getId() {
        return this.id;
    }

    public setId(int newId) {
        this.id = newId;
    }
}

Приведенный ниже код дает вам исключение с нулевым указателем.

public class School {

    Student obj_Student;

    public School() {
        try {
            obj_Student.getId();
        }
        catch(Exception e) {
            System.out.println("Null Pointer ");
        }
    }
}

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

public class School {

    Student obj_Student;

    public School() {
        try {
            obj_Student = new Student();
            obj_Student.setId(12);
            obj_Student.getId();
        }
        catch(Exception e) {
            System.out.println("Null Pointer ");
        }
    }
}
12
задан Benoit 16 September 2008 в 03:51
поделиться

6 ответов

Без любой оптимизации на поток через Ваш код линеен. Если Вы находитесь на строке 5 и одноэтапные, Вы ступаете для выравнивания 6. С оптимизацией на можно получить переупорядочение инструкции, развертывание цикла и все виды оптимизации.
Например:


void foo() {
1:  int i;
2:  for(i = 0; i < 2; )
3:    i++;
4:  return;

В этом примере, без оптимизации, Вы могли одноэтапный через код и поражать строки 1, 2, 3, 2, 3, 2, 4

С оптимизацией на Вы могли бы получить путь выполнения, который похож: 2, 3, 3, 4 или даже всего 4! (Функция ничего не делает после всех...),

Нижняя строка, отлаживая код с включенной оптимизацией может быть королевской болью! Особенно, если у Вас есть большие функции.

Обратите внимание, что включение оптимизации изменяет код! В определенной среде (безопасность критические системы), это недопустимо, и отлаживаемый код должен быть поставленным кодом. Должен отладить с оптимизацией на в этом случае.

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

    int* ptr = 0xdeadbeef;  // some address to memory-mapped I/O device
    *ptr = 0;   // setup hardware device
    while(*ptr == 1) {    // loop until hardware device is done
       // do something
    }

С оптимизацией прочь, это просто, и Вы отчасти знаете, что ожидать. Однако при включении оптимизации несколько вещей могли бы произойти:

  • Компилятор мог бы оптимизировать, в то время как блок далеко (мы init к 0, это никогда не будет 1),
  • Вместо того, чтобы получить доступ к памяти, доступ указателя мог бы быть перемещен в регистр-> Никакое Обновление ввода-вывода
  • доступ к памяти мог бы кэшироваться (не обязательно связанная компиляторная оптимизация)

Во всех этих случаях поведение было бы решительно другой и наиболее вероятной несправедливостью.

28
ответ дан 2 December 2019 в 03:49
поделиться

Другое решающее различие между отладкой и выпуском - то, как хранятся локальные переменные. Концептуально локальные переменные являются выделенным устройством хранения данных в стековом фрейме функций. Файл символов, сгенерированный компилятором, говорит отладчику смещение переменной в стековом фрейме, таким образом, отладчик может показать его Вам. Отладчик посмотрел на ячейку памяти, чтобы сделать это.

Однако это означает каждый раз, когда локальная переменная заменяется, сгенерированный код для той исходной строки должен записать значение обратно к корректному местоположению на стеке. Это очень неэффективно из-за памяти наверху.

В сборке конечных версий компилятор может присвоить локальную переменную регистру для части функции. В некоторых случаях это не может присвоить устройство хранения данных стека для него вообще (чем больше регистров машина имеет, тем легче это сделать).

Однако отладчик не знает, как регистры отображаются на локальные переменные для конкретной точки в коде (я не знаю ни о каком формате символа, который включает эту информацию), таким образом, это не может показать его Вам точно, поскольку это не знает, куда пойти, ища его.

Другая оптимизация была бы функциональным встраиванием. В оптимизированных сборках компилятор может заменить вызов к нечто () с фактическим кодом для нечто везде, это используется, потому что функция достаточно небольшая. Однако, когда Вы пытаетесь установить точку останова на нечто (), отладчик хочет знать адрес инструкций для нечто (), и больше нет простого ответа на это - могут быть тысячи копий нечто () байты кода, распространенные по Вашей программе. Отладочная сборка гарантирует, что существует где-нибудь, чтобы Вы поместили точку останова.

4
ответ дан 2 December 2019 в 03:49
поделиться

Оптимизация кода является автоматизированным процессом, который улучшает производительность во время выполнения кода при сохранении семантики. Этот процесс может удалить промежуточные результаты, которые являются unncessary для завершения выражения или функциональной оценки, но могут представлять интерес для Вас при отладке. Точно так же оптимизация может изменить очевидный поток управления так, чтобы вещи могли произойти в немного отличающемся порядке, чем, что появляется в исходном коде. Это сделано для пропуска ненужных или избыточных вычислений. Этот rejiggering кода может смешать с отображением между числами строки исходного кода и адресами объектного кода, мешающими отладчику следовать за потоком управления, поскольку Вы записали это.

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

После того как Вы рады, что Ваша программа работает правильно, можно включить оптимизацию для получения улучшенной производительности. Даже при том, что оптимизаторы довольно защищены в эти дни, это - все еще хорошая идея создать набор тестов хорошего качества, чтобы гарантировать что Ваши прогоны программы тождественно (с функциональной точки зрения, не рассматривая производительность) и в оптимизированном и в неоптимизированном режиме.

3
ответ дан 2 December 2019 в 03:49
поделиться

Ожидание состоит в том, чтобы отладочная версия была - отлажена! Устанавливая точки останова, единственное продвижение при наблюдении переменных, отслеживаний стека, и все остальное, что Вы делаете в отладчике (IDE или иначе) имеет смысл, если каждая строка непустых, не комментируют, что исходный код соответствует некоторой команде машинного кода.

Большая часть оптимизации смешивает с порядком машинных кодов. Развертывание цикла является хорошим примером. Общие подвыражения могут быть подняты из циклов. С включенной оптимизацией, даже самый простой уровень, можно пытаться установить точку останова на строке, которая, на уровне машинного кода, не существует. Когда-то Вы не можете контролировать локальную переменную из-за него сохраняемый в регистре ЦП, или возможно даже оптимизированный из существования!

2
ответ дан 2 December 2019 в 03:49
поделиться

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

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

Однако на языках, размещенных Виртуальной машиной как Java, оптимизация и отладка могут сосуществовать - даже во время отладки, JIT-компиляция к собственному коду продолжается, и только код отлаженных методов прозрачно преобразовывается в неоптимизированную версию.

Я хотел бы подчеркнуть, что оптимизация не должна изменять поведение кода, если используемый оптимизатор не является багги, или сам код является багги и полагается на частично неопределенную семантику; последний более распространен в многопоточном программировании или когда встроенный ассемблерный код также используется.

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

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

1
ответ дан 2 December 2019 в 03:49
поделиться

Если Вы отлаживаете на уровне инструкции, а не исходном уровне, очень много для Вас легче отобразить неоптимизированные инструкции назад на источник. Кроме того, компиляторы иногда являются багги в своих оптимизаторах.

В подразделении Windows в Microsoft все двоичные файлы выпуска создаются с отладочной информацией и полной оптимизацией. Символы хранятся в отдельных файлах PDB и не влияют на производительность кода. Они не поставлются с продуктом, но большинство из них доступно в Microsoft Symbol Server.

1
ответ дан 2 December 2019 в 03:49
поделиться