gcc 4.9.0の未定義の動作を使用した新しい最適化

バージョン4.9.0に切り替えると、gccユーザーを歓迎します。未定義の動作を使用した新しい最適化により、既存のコードを「破壊」(実際にクラック)できます。たとえば、以前にmemmove()と0標準ライブラリの他の機能。



たとえば、そのようなコードでは次のように記述されています。

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)を呼び出すと、nullポインターが逆参照されます(そしてプログラムがクラッシュします)。



一見すると、コンパイラが意図的にプログラムを壊したように見えます。 一部の読者は既にinり(特に「判読不能」なコード例)で一杯であり、それを表現するためにコメントを急いでいます。 早すぎます。 まず、標準C99でこれについて述べられていることを見てください。



セクション7.21は、 string.hヘッダーで宣言された「文字列関数」について説明します。7.21.1/ 2は、次のように述べています:「このサブセクションの特定の関数の説明がそうでない場合、関数が呼び出されたときに引数として渡されるポインターは7.1.4の要件を満たす許容値 memmove()関数は、7.21.2.2で説明されています。 「文字列関数」を指し、その説明は、入力でのNULLポインターの許容性については何も述べていません。



TL; DR; 7.1.4を見ると、「関数の引数に無効な値(<...>、nullポインターなど)<...>がある場合、動作は定義されていません」と表示されます。



したがって、3番目のパラメーターの値(バイト数)がゼロであっても、ヌルポインターをmemmove()に渡すと、未定義の動作が発生します。 コンパイラーは、これから次の結論を導き出します。ポインターがmemmove()に渡される場合、それはゼロ以外であると想定し、それに応じて残りのコードを最適化できます。 この素晴らしい出版物では、この考えを例とともに詳細に説明しています。



gcc 4.9.0を使用してMinGWで再生してみましょう

 #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を使用して配列にインデックスを付けるだけでは不十分です。



ドミトリー・メッシェリャコフ、

開発者製品部門



All Articles