...

четверг, 21 августа 2014 г.

Новые оптимизации с использованием неопределенного поведения в gcc 4.9.0

Отличные новости ждут пользователей gcc при переходе на версию 4.9.0 – новые оптимизации с использованием неопределенного поведения могут «сломать» (на самом деле — доломать) существующий код, который, например, сравнивает с нулем указатели, ранее переданные в memmove() и ряд других функций стандартной библиотеки.

Например, утверждается, что в таком коде:



int wtf( int* to, int* from, size_t count ) {
memmove( to, from, count );
if( from != 0 )
return *from;
return 0;
}




новый gcc может удалить сравнение указателя с нулем и в результате вызов wtf( 0, 0, 0 ) будет приводить к разыменованию нулевого указателя (и аварийному завершению программы).



На первый взгляд, выглядит так, как будто компилятор целенаправленно сломал программу. Отдельные читатели уже полны возмущения (особенно «невразумительным» примером кода) и спешат в комментарии, чтобы его высказать. Пока рано. Сначала стоит посмотреть, что сказано по этому поводу в Стандарте C99.

В разделе 7.21 описаны «строковые функции», объявляемые в заголовке string.h В 7.21.1/2 сказано следующее: «если в описании конкретной функции в данном подразделе не сказано иное, то указатели, передаваемые в качестве аргументов при вызове функции, должны иметь допустимые значения, соответствующие требованиям 7.1.4». Функция memmove() описана в 7.21.2.2, т.е. относится к «строковым функциям», в ее описании ничего не сказано о допустимости нулевых указателей на входе.


TL;DR; Смотрим в 7.1.4, там сказано «Если аргумент функции имеет недопустимое значение (такое как <…>, нулевой указатель) <…>, то поведение не определено».


Таким образом, передача нулевых указателей в memmove() приводит к неопределенному поведению, даже если значение третьего параметра (число байт) равно нулю. Компилятор делает из этого следующий вывод: если указатель передается в memmove(), можно считать, что он ненулевой, и оптимизировать остальной код соответствующим образом. Эта идея подробно и с примерами объяснена вот в этой замечательной публикации.


Попробуем это воспроизвести на MinGW с gcc 4.9.0



#include <stdio.h>
#include <string.h>

void magic1( char* to, char* from, size_t count )
{
memmove( to, from, count );
if( from == 0 ) {
printf( "null\n" );
} else {
printf( "not null\n" );
}
}

int main()
{
magic1( 0, 0, 0 );
return 0;
}




Компилируем:


gcc magic.c -O2 -o magic.exe





Запускаем полученный исполняемый файл – получаем в выдаче «not null».

Для сравнения, если вызов memmove() перенести ниже:



void magic2( char* to, char* from, size_t count) {
if( from == 0 ) {
printf( "null\n" );
} else {
printf( "not null\n" );
}
memmove( to, from, count );
}




то выдача будет ожидаемая: «null» — с новой оптимизацией работа программы может меняться в зависимости от того, стоит вызов memmove() выше или ниже сравнения указателя с нулем.

Это еще не все. Работа программы может измениться при замене библиотечной функции на «велосипед» или наоборот:



void mymemcpy( char* to, char* from, size_t count )
{
while( count > 0 )
{
*to++ = *from++;
count--;
}
}

void magic3( char* to, char* from, size_t count ) {
mymemcpy( to, from, count );
if( from == 0 ) {
printf( "null\n" );
} else {
printf( "not null\n" );
}
}




При вызове magic3( 0, 0, 0 ) программа выдает «null». В случае использования библиотечной memcpy() выдается «not null».

В описании настроек оптимизации описанная выше в явном виде не упоминается. Самой похожей выглядит -fdelete-null-pointer-checks, и действительно с настройкой -fno-delete-null-pointer-checks эта оптимизация отключается вместе с рядом других оптимизаций, полагающих, что ранее разыменованный указатель нет смысла сравнивать с нулем. Заметим, что в описанной выше оптимизации речь не идет о разыменовании указателя, а только о передаче указателя в качестве параметра строковых функций.


Вопреки распространенному мнению, по-настоящему переносимый код писать не так легко, как хотелось бы. Использовать size_t для индексирования массивов недостаточно.


Дмитрий Мещеряков,

департамент продуктов для разработчиков


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий