趣味プログラマによるOSS開発日誌

趣味で作っているOSSソフトウェアの紹介や関連技術の紹介、楽曲製作、Webデザイン勉強状況を紹介します。

intとunsigned intのシフト演算の違いについて

自作STGに固定小数点化を採用する時に、C++(C言語)におけるシフト演算で、多少悩んでしまったのでメモしておく。

C++のシフト演算では、intで変数を宣言した時と、unsigned intで変数を宣言した時とで振る舞いが異なる。
int型で変数を宣言した時には、符号bitが考慮されたシフト演算(算術シフト)となる。
それに対して、unsigned int型で変数を宣言した時には、符号bitが考慮されないシフト演算(論理シフト)となる。

具体的な例を以下に示す。

// 算術シフトと論理シフトの違いを示す例

#include <iostream>

const int INITIAL_BIT_PATTERN = 0xFF789ACD;	// 初期ビットパターン

// 任意の型を2進数で表示するテンプレート関数。
template < typename T >
void print_binary( T val )
{
	for( int i = sizeof( T ) * 8 - 1; i >= 0; --i ){
		std::cout << ( ( val >> i ) & 0x1 ) << " ";
	}
}

int main()
{
	int i;
	unsigned int ui;

	// 初期状態
	i = INITIAL_BIT_PATTERN;
	ui = INITIAL_BIT_PATTERN;
	std::cout << "[Before]" << std::endl;
	std::cout << "i -> ";
	print_binary( i );
	std::cout << std::endl;
	std::cout << "ui -> ";
	print_binary( ui );
	std::cout << std::endl;

	// 右シフト
	i >>= 1;	// 算術シフト
	ui >>= 1;	// 論理シフト
	std::cout << "[Right shift]" << std::endl;
	std::cout << "i -> ";
	print_binary( i );
	std::cout << std::endl;
	std::cout << "ui -> ";
	print_binary( ui );
	std::cout << std::endl;

	// 左シフト
	i = INITIAL_BIT_PATTERN;
	ui = INITIAL_BIT_PATTERN;
	i <<= 1;	// 算術シフト
	ui <<= 1;	// 論理シフト
	std::cout << "[Left shift]" << std::endl;
	std::cout << "i -> ";
	print_binary( i );
	std::cout << std::endl;
	std::cout << "ui -> ";
	print_binary( ui );
	std::cout << std::endl;
	
	return 0;
}

このプログラムをコンパイルして、実行した時の結果は以下のようになる。

[Before]
i -> 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 
ui -> 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 
[Right shift]
i -> 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 
ui -> 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 
[Left shift]
i -> 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 0 
ui -> 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 0

結果を見ればわかるように、右シフトを行うと、int型ではシフト前のMSBの値で埋められるのに対し、unsigned int型ではMSBは0となる。
このように違いが出るのは、int型で右シフトを行った時に、負数であっても1/2倍となる振る舞いを実現するためである。
同様にして右シフトについても、int型では右シフトの値は保存されるのに対し、unsigned int型では2番目のbitで書き換えられてしまう。

より詳しく理解するために、コンパイルされたプログラムのアセンブリコードを見てみる。

        sarl    $1, %eax                      # 算術右シフト(i>>1)
        movl    %eax, -12(%rbp)
        movl    -16(%rbp), %eax
        shrl    $1, %eax                      # 論理右シフト(ui>>1)
        movl    %eax, -16(%rbp)
        leaq    L_.str4(%rip), %rax
        movq    -24(%rbp), %rdi
        movq    %rax, %rsi
        callq   __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        movq    %rax, %rdi
        movq    -32(%rbp), %rsi
        callq   __ZNSolsEPFRSoS_E
        movq    -24(%rbp), %rdi
        movq    -40(%rbp), %rsi
        callq   __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        movl    -12(%rbp), %eax
        movl    %eax, %edi
        callq   __Z12print_binaryIiEvT_
        movq    -24(%rbp), %rdi
        movq    -32(%rbp), %rsi
        callq   __ZNSolsEPFRSoS_E
        movq    -24(%rbp), %rdi
        movq    -48(%rbp), %rsi
        callq   __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        movl    -16(%rbp), %eax
        movl    %eax, %edi
        callq   __Z12print_binaryIjEvT_
        movq    -24(%rbp), %rdi
        movq    -32(%rbp), %rsi
        callq   __ZNSolsEPFRSoS_E
        movl    $-8873267, -12(%rbp)
        movl    $-8873267, -16(%rbp)
        movl    -12(%rbp), %eax
        leal    (,%rax,2), %eax               # 算術左シフト(i<<1)
        movl    %eax, -12(%rbp)
        movl    -16(%rbp), %eax
        leal    (,%rax,2), %eax               # 算術左シフト(ui<<1)

どうやら左シフトは型では決まらないらしく、どちらも算術シフトとなった。
実際にソースコードのINITIAL_BIT_PATTERNを0x8F789ACDして実行した結果を以下に示す。
実行結果から、左シフトに関しては算術シフト演算が行われていることがわかる。

[Before]
i -> 1 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 
ui -> 1 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 
[Right shift]
i -> 1 1 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 
ui -> 0 1 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 
[Left shift]
i -> 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 0 
ui -> 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 0 1 0