Вызов free(NULL); «ничего не делает», а нетривиальная конструкция только для того, чтобы «ничего не делать», выглядит странно.
Вот другой гипотетический код, скорее всего, с ошибкой:
if( !ptr ) free(ptr);
Здесь, можно предположить, хотели проверить обратное условие, а получили код, который всегда вызывает free(), передавая нулевой указатель, т.е. с такими параметрами, что он «ничего не делает», зато в случае ненулевого указателя free() не вызывается, а это, скорее всего, приводит к утечке памяти.
В обоих случаях код содержит заведомо «бессмысленный» вызов free(), а статический анализ, среди прочего, может довольно хорошо находить такие «бессмысленные» вызовы.
Интересно получилось. При верстке был использован тег <source> без атрибута lang. При просмотре «инспектором» Огнелиса было видно, что в HTML страницы оказался элемент <source lang=«brainfuck»> Прописал lang=«bash».
Предсказание вызова во время работы программы — это хорошо, но процессор вынужден работать с машинным кодом, который можно было бы упростить, используя знания о том, каким конструкциям языка высокого уровня он соответствует. Вот например:
class Derived : public Base {
public:
virtual int Magic() { return 100500; }
};
void someFarAwayFunction()
{
Base* base = new Derived();
std::vector<int> somethingUseful;
//blahblahblah, fill vector
{ // this block can be removed completely
int uselessSum = std::accumulate( somethingUseful.begin(),
somethingUseful.end(), 0);
if( base->Magic() < 0 ) {
printf( "%d", uselessSum );
}
}
}
Если компилятор может понять, что здесь гарантированно вызывается именно Derived::Magic(), которая возвращает положительное число, он может прийти к выводу, что вызов printf() никогда не выполняется, а из этого следует, что можно попробовать удалить и код вычисления параметров для этого вызова. В результате весь внутренний блок можно удалить, и тогда он просто не дойдет до процессора.
В Стандарте действительно не сказано ничего о вариантах неопределенного поведения. В статье, очевидно, речь идет о возможных вариантах работы конкретного результата компиляции программы конкретной версией конкретного компилятора с конкретными настройками. Одни компиляторы вообще неспособны к описанной оптимизации, у других она включается в зависимости от настроек (в достаточно новом gcc она включается автоматически, начиная с уровня -O2). В зависимости от этих факторов и возможны разные варианты работы программы.
Определение undefined behavior из Стандарта C++03 (1.3.12)
behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements.
[...], на которое этот Стандарт не налагает никаких требований — допускается любое поведение, в том числе и запуск Emacs. В принципе, никто не мешает разработчикам компилятора специально сделать неопределенное поведение разнообразным — например, по пятницам программа будет запускать Emacs, а по остальным — выполнять действие, приводящее к аварийному завершению программы, но пользователи не очень это оценят.
Это не те пункты. В Стандарте C++03 нужное определение дано в 1.3.12
undefined behavior [defns.undefined]
behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements.
imposes no requirements означает «не налагает никаких требований» — допускается любое поведение.
Как вы это определили? V567 и V611 указывают на дефекты, провоцирующие неопределенное поведение, код с такими дефектами непереносим не только на другие компиляторы, но и на другие версии того же компилятора.
Анализатор как раз целенаправленно ищет типовые дефекты, делает это довольно быстро, не устает, обычно выдает хорошо продуманные предупреждения. Запустить анализатор хотя бы из любопытства (кто-то рассказал, что это такая крутая штука, которая в его программе воооот такую ошибку «на ровном месте» нашла, надо попробовать) и увидеть крайне неожиданное сообщение о возможном удалении вызова memset(), по-моему, гораздо реальнее, чем пристально рассмотреть машинный код всей программы и найти последствия удаления вызова.
Чтобы он полез в дизассемблер, он должен сначала пронаблюдать, что «что-то не так», и взяться это исследовать. Например, он заметит, что в конец сохраняемого программой файла каждый раз дописывается один и тот же блок случайных на вид данных. Если программа записывает, например, JPEG, в котором после кодированного изображения можно безболезненно записывать что угодно, он может и полениться выяснять, в чем именно проблема.
Чтобы можно было учиться на ошибках, кто-то должен на эти ошибки указать. Предположим, студенты используют memset(), чтобы перезаписать массив, в котором хранился ключ шифрования, а компилятор удалил вызов memset(), потому что смог определить, что этот вызов в именно этом месте, согласно Стандарту, не влияет на так называемое «наблюдаемое поведение» (описано подробно тут). Как вы думаете, насколько реально, что студенты это найдут?
Причина проста: постоянные проблемы у поставщика электричества — до 20 аварий в месяц. Конечно, бензогенератор подстраховывал, но в таких условиях очень быстро выходили из строя аккумуляторы, их приходилось менять каждые шесть-восемь месяцев.
Почему при прежней схеме, когда аккумуляторы использовались для питания нагрузки довольно редко и короткое время (пока не запустится генератор), они изнашивались так сильно, что вы решили перейти к новой схеме, при которой они будут использоваться для питания нагрузки каждую ночь? Как достигается уменьшение износа при явно более интенсивном использовании?
Неправильное сравнение — в изначальном коде следующая за циклом строка кода использует переменную с тем же именем, что и переменная цикла, а в вашем коде просто выводится фиксированная строка.
Исходный вариант не предполагает вызова функции, поэтому его можно использовать внутри static_assert (и его аналогов для компиляторов, которые не поддерживают static_assert) и как число элементов массива фиксированного размера. Предлагаемый вами вариант так использовать нельзя.
Вот другой гипотетический код, скорее всего, с ошибкой:
if( !ptr ) free(ptr);
Здесь, можно предположить, хотели проверить обратное условие, а получили код, который всегда вызывает free(), передавая нулевой указатель, т.е. с такими параметрами, что он «ничего не делает», зато в случае ненулевого указателя free() не вызывается, а это, скорее всего, приводит к утечке памяти.
В обоих случаях код содержит заведомо «бессмысленный» вызов free(), а статический анализ, среди прочего, может довольно хорошо находить такие «бессмысленные» вызовы.
Если компилятор может понять, что здесь гарантированно вызывается именно Derived::Magic(), которая возвращает положительное число, он может прийти к выводу, что вызов printf() никогда не выполняется, а из этого следует, что можно попробовать удалить и код вычисления параметров для этого вызова. В результате весь внутренний блок можно удалить, и тогда он просто не дойдет до процессора.
[...], на которое этот Стандарт не налагает никаких требований — допускается любое поведение, в том числе и запуск Emacs. В принципе, никто не мешает разработчикам компилятора специально сделать неопределенное поведение разнообразным — например, по пятницам программа будет запускать Emacs, а по остальным — выполнять действие, приводящее к аварийному завершению программы, но пользователи не очень это оценят.
imposes no requirements означает «не налагает никаких требований» — допускается любое поведение.
litehtmlconv.azurewebsites.net/api/pics/1j769eplayx8opso56ahn-fr3
Какой смысл хранить картинки для поста именно в Azure Websites, да еще отдавать их не как статику?
Почему при прежней схеме, когда аккумуляторы использовались для питания нагрузки довольно редко и короткое время (пока не запустится генератор), они изнашивались так сильно, что вы решили перейти к новой схеме, при которой они будут использоваться для питания нагрузки каждую ночь? Как достигается уменьшение износа при явно более интенсивном использовании?