ページ 11

2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月04日(日) 22:46
by シエル
2バイト文字をUINT型に置き換える方法を調べていたところ、
2バイト文字は先行コードと呼ばれるものと、文字コードの2バイトの構成になっていることが分かりました。
さらに調べていたところ、
下記のサンプルがネットに転がっていました。

下記のコードの意味をお伺いしたいのですが、
IsDBCSLeadByte関数で、先行バイトか調べた後に、
(BYTE)c[0]<<8 | (BYTE)c[1]でビット演算をしていますが、この意味が具体的に良く分かりません。

自分の解釈としては先行コードはc[0]に入っており、それを左に8ビットシフトさせて0にし、
c[1]に入ってる文字コードとの論理輪を求め、文字コードだけを取り出しているのかな、
と思いましたが、合っていますでしょうか?
もしそうだとすると、c[1]の値だけcodeに代入すればいいとも思ったのですがどうなのでしょうか?
また、BYTE型にキャストしている意味も分かりません。


char *c = "あ";
UINT code = 0;
if(IsDBCSLeadByte(*c))
   code = (BYTE)c[0]<<8 | (BYTE)c[1];
else
   code = c[0];


どなたか分かる方、具体的に説明していただけますでしょうか?
ビット演算自体苦手なので、考え方自体が破綻していたら申し訳ありません。。。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 00:36
by Justy
> code = (BYTE)c[0]<<8 | (BYTE)c[1];
 まず BYTE型にキャストしているのは、キャストすることで符号無しであることを確実にする為です(後述)。

 次にその符号なしBYTE型のデータを <<8でシフトするとき、c[0]は整数拡張(汎整数昇格)により
int型として扱われ、それを 8ビットシフトすることになります。
 なので、c[0]が 0でない限りシフトの結果は 0にはなりません。

 そして最後に同じく整数拡張した c[1]と論理和を求めることで2バイトの文字が codeに格納されます。


 例を挙げます。
 環境的に charが符号有り Shift-JISだと仮定すると "あ"の文字は c[0]に -126、c[1]に -86が入っています。
 これを BYTE型にすることで c[0]は 0x82、c[1]は 0xa0になります。
 c[0]を 8ビット左にシフトすると 0x8200となり、これと c[1]の論理和で結果 codeへは 0x82a0が代入される
ことになります。


>BYTE型にキャストしている意味も分かりません
 そもそも符号付き整数の負の値を左にシフトするのは言語的には未定義な動作となりますので、
符号無しに変換するか、符号有りでも正であることを保証した上で左シフトする必要があります。

 その上で、もしBYTE型にキャストしなかった場合を先ほどの"あ"をある特定環境下での挙動をみてみますと
c[0]を 8ビット左にシフトするとき c[0]の -126は整数拡張により int型として扱われ 0xffffff82とした
状態で 8bit左にシフトします。
 その結果は 0xffff8200となり、c[1]も整数拡張により 0xffffffa0となっていますので
2つの論理和の結果は 0xffff82a0という値になってしまいます。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 11:09
by シエル
Justyさん。本当にいつも助かります。ありがとうございます!

>>次にその符号なしBYTE型のデータを <<8でシフトするとき、c[0]は整数拡張(汎整数昇格)により
>>int型として扱われ、それを 8ビットシフトすることになります。
>>なので、c[0]が 0でない限りシフトの結果は 0にはなりません。
汎整数昇格という言葉は初めて知りました。
ネットで調べると、演算されるときにint型で表現できる値であれば、int型に暗黙的に変換されているらしいですね。
この文章は理解できました。


>>環境的に charが符号有り Shift-JISだと仮定すると "あ"の文字は c[0]に -126、c[1]に -86が入っています。
>>これを BYTE型にすることで c[0]は 0x82、c[1]は 0xa0になります。
charが符号有りだと、正の数は127までしか表現できないので、130という値は-126になってしまう。
それをBYTE型(unsigned char)にキャストすると255まで表現できるので、0x82(130)の正の数で表せるという
認識で大丈夫でしょうか?


>>c[0]を 8ビット左にシフトすると 0x8200となり、これと c[1]の論理和で結果 codeへは 0x82a0が代入される
>>ことになります。
計算の意味は分かるのですが、なぜc[0]は上位ビット、なぜc[1]は下位ビットで計算をするのでしょうか?
"あ"という文字が、c[0],c[1]の中にどのように分割されて値が入っているのでしょうか?
深く考えずにそういうものだと思ってたほうがいいですかね?



>>そもそも符号付き整数の負の値を左にシフトするのは言語的には未定義な動作となりますので、
>>符号無しに変換するか、符号有りでも正であることを保証した上で左シフトする必要があります。
>>その上で、もしBYTE型にキャストしなかった場合を先ほどの"あ"をある特定環境下での挙動をみてみますと
>>c[0]を 8ビット左にシフトするとき c[0]の -126は整数拡張により int型として扱われ 0xffffff82とした
>>状態で 8bit左にシフトします。
>>その結果は 0xffff8200となり、c[1]も整数拡張により 0xffffffa0となっていますので
>>2つの論理和の結果は 0xffff82a0という値になってしまいます。
この文章の意味は理解できました。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 12:21
by へろりくしょん
>charが符号有りだと、正の数は127までしか表現できないので、130という値は-126になってしまう。
>それをBYTE型(unsigned char)にキャストすると255まで表現できるので、0x82(130)の正の数で表せるという
>認識で大丈夫でしょうか?

キャストしないと整数拡張により、内部のビットパターンが破綻するからでしょう。


>計算の意味は分かるのですが、なぜc[0]は上位ビット、なぜc[1]は下位ビットで計算をするのでしょうか?
>"あ"という文字が、c[0],c[1]の中にどのように分割されて値が入っているのでしょうか?
>深く考えずにそういうものだと思ってたほうがいいですかね?

SIFT_JISがそういう仕様だからというのが答えになりますが、シフトを行わずに単純にキャストした場合。

unsigned short moji = *(unsigned short*)"あ";

この場合は、エンディアンの差を吸収出来ません。
リトルエンディアンでは、上位8ビットと、下位8ビットが入れ替わります。

unsigned short moji2 = (BYTE)("あ"[0])<<8 | (BYTE)("あ"[1]);

とした場合のコードと結果を比べてみるとわかりやすいです。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 13:25
by シエル
へろりさん!ありがとうございます!

リトルエンディアンとかはまだ意味がわかってないので、完全に理解は出来てませんが、
テストしながら理解していきます。

最後に質問ですが、
char *c = "あ"とした場合は、この「あ」という文字はSHIFT_JISとして処理されるのでしょうか?
ほかにもUNICODE等の文字コードがあるのに、なぜSHIFT_JISとして処理されるのかが分かりません。
プロジェクトの文字セットの設定とかが関係してくるのでしょうか?

また変なこと言ってたらすみません。。。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 13:37
by へろりくしょん
>キャストしないと整数拡張により、内部のビットパターンが破綻するからでしょう。

ちょっと分かりにくかったと思うので補足。

内部のビットをどのように解釈するかその方法が変わるのではなく、ビットパターンそのものが壊れてしまいます。

unsigned char hoge = 130;
signed char hoge2 = 130;
unsigned char hoge3 = -126;
signed char hoge2 = -126;

これらの値はどちらも0x82 ビットパターンは同じです。
0x82 という値が、130でも-126でもどちらでもかまわないのです。
要は10000010(2)であればいいのです。

ところが、整数拡張でint型になると話が変わってきます。

130は正数ですから、
00000000000000000000000010000010(2)になります。

しかし-126は負数ですから、
11111111111111111111111110000010(2)になりますね。

これでは不都合です。


ですから、わかりやすく? 必要な部分だけをマスクして
unsigned int hoge = "あ"[0] << 8 & 0xFF00 | "あ"[1] & 0xFF;
なんてしても0x82A0 という数値は得られるはずです。


(BYTE)でキャストを行う理由は、単に鼻から悪魔を出さないためであるだけなのでしょうけども、計算上でも不都合がありますよということで、鼻から悪魔はスルーしてください。


ところで、0xFFFF8200 と 0xFFFFFFA0 の論理和は 0xFFFFFFA0 ではありませんか。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 13:52
by へろりくしょん
>char *c = "あ"とした場合は、この「あ」という文字はSHIFT_JISとして処理されるのでしょうか?
>ほかにもUNICODE等の文字コードがあるのに、なぜSHIFT_JISとして処理されるのかが分かりません。

環境依存です。 少なくとも"あ"は、基本実行文字集合・基本ソース文字集合のいずれにも含まれません。


>プロジェクトの文字セットの設定とかが関係してくるのでしょうか?

プロジェクトというのがよくわかりませんが、VisualStudioで作成するプロジェクトのプロパティの事を言ってるのでしたら、あれはTCHAR型をどう扱うかってだけだと思いますよ。 たぶん。

Unicodeの場合 TCHAR型 は unsigned short型。 で、_T("あ") は、L"あ"
マルチバイト文字の場合 TCHAR型は char型。 で、_T("あ")は、"あ"

ていうだけの設定かと。 多分。 よくはわかりません。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 14:23
by シエル
補足説明ありがとうございます。
あとちょっとで理解できそうなので、何回も読み直して理解します!

プロジェクトの文字セットの件は、おっしゃるとおりVisualstdioのプロジェクトのプロパティのことです。
こちらは今回の件では直接関係なさそうですね。

あとは自分で何とかします!ありがとうございました!


追記:
解決し忘れました。 画像

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 14:57
by toyo
>char *c = "あ"とした場合は、この「あ」という文字はSHIFT_JISとして処理されるのでしょうか?
>ほかにもUNICODE等の文字コードがあるのに、なぜSHIFT_JISとして処理されるのかが分かりません。

これはファイルのエンコードに関係します
ファイルを「名前を付けてファイルを保存」にした場合保存ボタンにリストボックスがついてきます
これで「エンコード付きで保存」を選ぶとそのファイルの文字コードや改行コードを変えられます

サブバージョンがらみでファイルをUTF-8にしたかったときこの変更方法がわからずに苦労しました

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 15:27
by シエル
toyoさん!情報ありがとうございます。
覚えときます!

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 22:51
by ISLe
Visual C++だとソースファイルの文字コードに関係なく、"あ"がShiftJISで、L"あ"がUnicodeですよ。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月05日(月) 22:57
by シエル
ISLeさん!最近はお世話になってます!
Visualc++ならソースファイルの文字コードは関係無いんですね。頭の中で上書きしときます。

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月06日(火) 02:26
by Justy
>ところで、0xFFFF8200 と 0xFFFFFFA0 の論理和は 0xFFFFFFA0 ではありませんか
 おっと、その通りですね。
 ご指摘ありがとうございます。


>No:55984
>  その結果は 0xffff8200となり、c[1]も整数拡張により 0xffffffa0となっていますので
> 2つの論理和の結果は 0xffff82a0 [color=red" face="sans-serif]0xffffffa0
という値になってしまいます。[/color]

Re:2バイト文字をUNIT型に置き換える方法

Posted: 2010年7月06日(火) 05:56
by toyo
ISLeさん訂正ありがとうございます
確認したら確かにファイルの文字コード関係なくShift_JISとしてコンパイルされました
勉強になりました