ページ 11

ビット演算と戻り値

Posted: 2012年3月12日(月) 22:43
by

コード:

unsigned short get2 (unsigned char *s){
----略------
return s[0] << 8 | s[1];
}
戻り値の部分では一体どのような手順で、どのような演算が行われているのでしょうか?

また、sは1バイトの変数であるのにどうしてこの関数は2バイトの変数であるunsigned short型の値を返すことができるのでしょうか?

Re: ビット演算と戻り値

Posted: 2012年3月12日(月) 23:04
by box
白 さんが書きました: また、sは1バイトの変数であるのにどうしてこの関数は2バイトの変数であるunsigned short型の値を返すことができるのでしょうか?
sは1バイト、というのは勘違いでありましょう。
sの型はunsigned char *です。
unsigned charではありません。

Re: ビット演算と戻り値

Posted: 2012年3月13日(火) 00:15
by softya(ソフト屋)
sはunsigned charポインタですので、s[0]なら確かに1バイトですがsは1バイトの変数ではありませんね。
さて、<<と言う演算子の意味はご存知でしょうか?上のポインタであると言うことと<<演算子が重要ポイントなのですが。

Re: ビット演算と戻り値

Posted: 2012年3月13日(火) 20:33
by
s[0],s[1]・・・がそれぞれ1バイトのunsigned char型であるということを書きたかったのですが、あきらかに言葉不足でした。もうしわけありません。

<<演算子は 
aを変数,xを定数としたとき 
a << x はaをxビット分だけ左にずらす演算子だと思っています。
例・1バイトの変数
10011011(二進数表記)にたいして << 3とすると 11011000を返す

 ポインタであることをどのように利用されているのかは、分かりません。

Re: ビット演算と戻り値

Posted: 2012年3月13日(火) 21:20
by softya(ソフト屋)
白 さんが書きました:aを変数,xを定数としたとき 
a << x はaをxビット分だけ左にずらす演算子だと思っています。
例・1バイトの変数
10011011(二進数表記)にたいして << 3とすると 11011000を返す
いえ、1バイトの変数に対して<<3演算してもこうはなりません。
実際にprintfして確認してみて下さい。
白 さんが書きました:ポインタであることをどのように利用されているのかは、分かりません。
この場合のポインタは配列として処理しているだけなのですが、s[0]とs[1]は何処の値をそれぞれ何バイト取り出すことになるでしょうか?

Re: ビット演算と戻り値

Posted: 2012年3月13日(火) 22:04
by box
何となく、質問者さんが行ないたいことは↓こういうことではないかなぁ、などと勝手に想像しています。

コード:

#include <stdio.h>

int main(void)
{
    unsigned char c = 0x9b, d = c << 3;

    printf("%x %x\n", c, d);
    return 0;
}
だとすると、質問者さんが
白 さんが書きました: 例・1バイトの変数
10011011(二進数表記)にたいして << 3とすると 11011000を返す
こんな風に書かれたのは至極まっとうなことでありまして、
softya(ソフト屋) さんが書きました: いえ、1バイトの変数に対して<<3演算してもこうはなりません。
実際にprintfして確認してみて下さい。
これは筋が違うのではないかなぁ、などと勝手に想像したりしています。
まあ、想像がはずれているだけかもしれないですけれど。

Re: ビット演算と戻り値

Posted: 2012年3月13日(火) 22:14
by
>>この場合のポインタは配列として処理しているだけなのですが、s[0]とs[1]は何処の値をそれぞれ何バイト取り出すことになるでしょうか?

メモリ:1|2|3|4|5|6|7|8|8|7|6|5|4|3|2|9 (右に向かってメモリ上のアドレスが大きくなっていく)
ポインタsが指し示すのが上記の1のアドレスだとすると、
s[0] = 1|2|3|4|5|6|7|8
s[1] = 8|7|6|5|4|3|2|9
のように、s[0],s[1]ともに1バイトずつ取り出す
 こういうことでしょうか?


>>いえ、1バイトの変数に対して<<3演算してもこうはなりません。
実際にprintfして確認してみて下さい。

確認用にプログラムを作ってはみたのですが、
・1バイトの変数
10011011(二進数表記)にたいして << 3とすると 11011000を返す
となってしまっています。
以下に確認用に作ったプログラムのソースを載せるので、問題点を教えてください。お願いします。

コード:

#include <stdio.h>

void put_2bit(unsigned char x);

int main(void){
	unsigned char a = 155;
	int i;

	int b = 0x1f3d;

	printf("%d\n",b);
	printf("%d\n\n",(b << 3));

	printf("%d\n",(a&0x00ff));
	printf("%d\n\n",((a << 3)&0x00ff));

	put_2bit(a);
	put_2bit(a << 3);

	return 0;
}

void put_2bit(unsigned char x){
	int i;

	for(i = 0;i < 8;i++){
		if(((x >> (7 - i))&1) == 1){
			putchar('1');
		}else{
			putchar('0');
		}
	}

	printf("\n");

	return ;
}

Re: ビット演算と戻り値

Posted: 2012年3月14日(水) 00:16
by softya(ソフト屋)
白 さんが書きました:メモリ:1|2|3|4|5|6|7|8|8|7|6|5|4|3|2|9 (右に向かってメモリ上のアドレスが大きくなっていく)
ポインタsが指し示すのが上記の1のアドレスだとすると、
s[0] = 1|2|3|4|5|6|7|8
s[1] = 8|7|6|5|4|3|2|9
のように、s[0],s[1]ともに1バイトずつ取り出す
 こういうことでしょうか?
たしかに1バイトづつ取り出しますが、
1|2|3|4|5|6|7|8|8|7|6|5|4|3|2|9
はビット?バイト?のどちらを表しているんでしょうか?|区切り1つが1バイトですか?|区切り1つが1ビットだと0か1しか格納できないはずです。
バイト区切りなら、s[0]とs[1]の値を16進数で書いてみて下さい。
白 さんが書きました:10011011(二進数表記)にたいして << 3とすると 11011000を返す
となってしまっています。
以下に確認用に作ったプログラムのソースを載せるので、問題点を教えてください。お願いします。
わざわざ上位ビットをと0x00ffで削っているのは何故でしょう。

コード:

unsigned short get2 (unsigned char *s){
----略------
return s[0] << 8 | s[1];
}
この関数の何処の部分が該当するんでしょうか?
私には0x00ffに該当する部分は無いと思いますが。
戻り型はunsigned shortですので1バイトでは有りません。

Re: ビット演算と戻り値

Posted: 2012年3月14日(水) 01:27
by ISLe
質問にあるコードの左ビットシフト演算子の左オペランドはunsigned charですが、演算時にはint型に昇格します。
なのでunsigned charなのに上位にあふれたビットが残ります。
汎整数拡張とか整数拡張とか汎整数昇格で検索してみてください。

コード:

return s[0] << 8 | s[1];
の部分では
  1. s[0]がビットシフト演算子のオペランドとしてint型に昇格→ビットシフト演算子の結果はint型
  2. 上の結果を元に、ビット論理和演算子の右オペランドs[1]もint型に昇格→ビット論理和演算子の結果はint型
  3. 式全体ではint型の結果をunsigned short型に丸めて戻り値
という流れになります。

Re: ビット演算と戻り値

Posted: 2012年3月14日(水) 17:21
by
>> softya(ソフト屋) さん

>>メモリ:1|2|3|4|5|6|7|8|8|7|6|5|4|3|2|9 (右に向かってメモリ上のアドレスが大きくなっていく)
は、1ビット区切りです。16個の区切りをつけるのに番号を付れば楽に区切れたので本来記録できないはずの値を載せてしまいました。すみません。

メモリ:1|1|1|1|1|0|0|1|1|1|1|0|1|0|0|0(1bit単位で計16bit、右に向かってアドレスが大きくなる)
ポインタsが指し示すのが上記メモリの最左端とすると
2進数 16進数
s[0] = 1|1|1|1|1|0|0|1 0xf9
s[1] = 1|1|1|0|1|0|0|0 0xe8

これでいいのでしょうか?

>>わざわざ上位ビットをと0x00ffで削っているのは何故でしょう。
自分でもなぜ削ったのか分からなくなりました。昨夜の時点ではなにか目的があって削ったのだとは思うのですが・・・
 ここから先は推測でしかないのですが、

コード:

put_2bit(a);
put_2bit(a << 3);
で出力される値と

コード:

printf("%d\n",a);
printf("%d\n\n",a << 3);
で出力される値が違ったので無理やり合わせたのだと思います。


>>ISLeさん
上記と同じようにメモリ:1|1|1|1|1|0|0|1|1|1|1|0|1|0|0|0(1bit単位で計16bit、右に向かってアドレスが大きくなる)
ポインタsが指し示すのが上記メモリの最左端とすると
2進数 16進数
s[0] = 1|1|1|1|1|0|0|1 0xf9
s[1] = 1|1|1|0|1|0|0|0 0xe8
と設定すると

1. s[0](1|1|1|1|1|0|0|1) << 8 → 1|1|1|1|1|0|0|1|0|0|0|0|0|0|0|0 というように2バイトに昇格

2. 1で2バイトに昇格したので自動的にs[1]も2バイトのint型に昇格する
s[1]の昇格後= 0|0|0|0|0|0|0|0|1|1|1|0|1|0|0|0

3. 1|1|1|1|1|0|0|1|0|0|0|0|0|0|0|0 | 0|0|0|0|0|0|0|0|1|1|1|0|1|0|0|0
を計算することになり、最終的な結果は
1|1|1|1|1|0|0|1|1|1|1|0|1|0|0|0
となり、戻り値はunsigned short型で定義されているために、1|1|1|1|1|0|0|1|1|1|1|0|1|0|0|0を丸める(”x型に丸める”というのはx型が記録できる形にするということですか?)

コード:

return s[0] << 8 | s[1];
上記の処理はつまり、ポインタsからの連続する16ビットを2バイトの変数(unsigned short)に格納できるようにするということでしょうか?

Re: ビット演算と戻り値

Posted: 2012年3月14日(水) 18:31
by beatle
ISLe さんが書きました:質問にあるコードの左ビットシフト演算子の左オペランドはunsigned charですが、演算時にはint型に昇格します。
白 さんが書きました:1. s[0](1|1|1|1|1|0|0|1) << 8 → 1|1|1|1|1|0|0|1|0|0|0|0|0|0|0|0 というように2バイトに昇格

2. 1で2バイトに昇格したので自動的にs[1]も2バイトのint型に昇格する
ISLeさんは「int型に昇格」と言っています。2バイトになるとは言っていません。もちろん、int型が2バイトの機械では2バイトに昇格することになります。

それから、ビットごとにアドレスを振る考え方は良くないと思います。C言語で考えられる最小の単位はバイトですから、バイト単位でアドレスを考えて下さい。

Re: ビット演算と戻り値

Posted: 2012年3月14日(水) 18:37
by softya(ソフト屋)
書き忘れです。ややこしい話ですがunsigned short型とunsigned int型のバイト数は実は処理系依存なのですが、それを言っていると複雑なので一般的なPCの環境(x86系Linux/WindowsでVc++やgccを利用)と想定して話を進めます。
この場合unsigned short型は2バイトでunsigned int型は4バイトです。

int型に昇格(暗黙の型変換)については、char,short,intの計算時は無条件にint型に昇格します。この場合unsigned なのでunsigned ing型に昇格します。
白 さんが書きました:2. 1で2バイトに昇格したので自動的にs[1]も2バイトのint型に昇格する
なので中途半端に2バイトに昇格することはありません。それと昇格時にunsigned の場合は上位ビットは0と想定されます(これも処理系依存)。
白 さんが書きました:上記の処理はつまり、ポインタsからの連続する16ビットを2バイトの変数(unsigned short)に格納できるようにするということでしょうか?
2バイトの情報を得るという機能以外に無条件にビックエンディアンの形で値を得ることが出来ると言う点が重要です。
x86系CPUはリトルエンディアン系なので、こういう事をしないとビックエンディアンの値を得ることが出来ません。

Re: ビット演算と戻り値

Posted: 2012年3月14日(水) 22:45
by ISLe
暗黙の型変換に含まれる汎整数拡張(整数拡張,汎整数昇格とも)は、元がunsignedでもsigned int型で表現できる場合はsigned int型に変換します。

Re: ビット演算と戻り値

Posted: 2012年3月15日(木) 18:20
by
 環境を書き忘れていました。すみませんでした。
OS winXp 32bit
コンパイラ VC++2008
以下上記の環境を前提条件としてお願いします。

>>beatleさん
>>softya(ソフト屋)さん
>>ISLeさん
計算結果が
・signed int型で表せるならばsigned int型に昇格
・signed int型で表せずにunsigned int型で表せるならばunsigned int型に昇格
ということでしょうか?
もし上記のように昇格するとすると signed int型でもunsigned int型でも表せないものはどのような扱いになりますか?

コード:

unsigned int promote = 0xffff;

	printf("%d\n",promote);
	promote++;
	printf("%d\n",promote);
コードを書いて試してみたのですが、
65535
65536
と表示されました。unsigned int型は上記の環境では0~65535の範囲しか表せないはずなのに、どうして65536と表示されてしまうのですか?

コード:

	printf("%d\n",promote);
	printf("%d\n",promote+1);
ならば、unsigned int型では表せない結果がさらに昇格したのかとも思えるのですが・・・

Re: ビット演算と戻り値

Posted: 2012年3月15日(木) 18:27
by softya(ソフト屋)
VC++のunsigned int型は4バイトです。4バイトですので数値の上限は2の32乗-1で4294967295です。
2バイトで試したいならunsigned short型ですね。

[補足]
C/C++は動的に変数の扱いが変わることはありません。なので変数が自動的に昇格することはありません。

Re: ビット演算と戻り値

Posted: 2012年3月15日(木) 18:53
by
printf("%d\n",sizeof(unsigned int));
で確認してみましたが4バイトでした。
また、読み返してみるとprintfのフォーマット指定しもunsigned intを表示させるなら%uでした。

コード:

	unsigned int promote = 0xffffffff;

	printf("%u\n",promote);
	promote += 1;
	printf("%u\n",promote);
	printf("size:%d\n",sizeof(unsigned int));
上記のようにして試してみたところ
4294967295
0
4
と出力されたので、unsigned int型以上に昇格することはないと受け取ってもいいのでしょうか?

Re: ビット演算と戻り値

Posted: 2012年3月15日(木) 19:02
by softya(ソフト屋)
白 さんが書きました:上記のようにして試してみたところ
4294967295
0
4
と出力されたので、unsigned int型以上に昇格することはないと受け取ってもいいのでしょうか?
受け取って良いと思います。

調度良い記事があるので読んでみて下さい。

「汎整数拡張 - Wikipedia」
http://ja.wikipedia.org/wiki/%E6%B1%8E% ... 1%E5%BC%B5
「汎整数拡張 ‐ 通信用語の基礎知識」
http://www.wdic.org/w/TECH/%E6%B1%8E%E6 ... 1%E5%BC%B5

Re: ビット演算と戻り値

Posted: 2012年3月15日(木) 19:04
by beatle
白さんは、多分「ANSI C言語辞典」を一冊持っていると役立つだろうと思います。
汎整数拡張の話とか、ビット演算の話(符号あり整数にビット演算すると処理系定義になる話とか)も乗っています。

とオススメしようと思ったのですがもう絶版なのかな・・・?
新ANSI C言語辞典

Re: ビット演算と戻り値

Posted: 2012年3月15日(木) 22:08
by ISLe
わたしの回答は最初の質問のコードに対してであり、汎整数拡張もビットシフト演算子に付随して言及したものです。
暗黙の型変換のルールについて興味があるなら規格をあたってください。

便利とはいえないですけど日本工業標準調査会のサイトのJIS検索でC/C++プログラミング言語の規格を読むことができます。
JIS規格番号はCがX3010、C++がX3014です。

printfは可変個数引数を取るので汎整数拡張の対象になります。
暗黙の型変換については大ざっぱに言って自動的にサイズの大きい型に昇格する仕組みです。

コード:

	unsigned int u = 0xffffffff;
	printf("size:%d\n",(int)sizeof(u + 1LL));

Re: ビット演算と戻り値

Posted: 2012年3月17日(土) 00:34
by
>>softya(ソフト屋)さん
さっそく参考記事の方読んでみます。

>>beatleさん
「新ANSI C言語辞典」を次に書店を訪れたときに探してみます。

最初の質問内容からだんだんとずれていってしまい、もうしわけありませんでした。

 全くとっかかりのつかめなかった

コード:

unsigned short get2 (unsigned char *s){
----略------
return s[0] << 8 | s[1];
}
をどうにかこうにか迫って行けそうになってきたので、ちゃんと理解できているかを実際にコードを書いて確かめてみたいと思います。
 boxさん、softya(ソフト屋)さん、ISLeさん、beatleさん今回は本当にありがとうございました。自分一人では何を調べれば理解できるようになるのかすら分からず途方に暮れていました。ありがとうございまいした.