5: 2007-03-17 (土) 22:25:26 |
現: 2024-01-06 (土) 22:37:40 ゲスト[htonJexNzZc] |
| | | |
| | | |
- | 1バイトは2進数で00000000〜11111111。10進数に直すと0〜255までとなる。逆に言えば、たった255しか表現できない困った単位ともいえる。 | + | 1バイトは2進数で00000000〜11111111。10進数に直すと0〜255までとなる。逆に言えば、たった256種類しか表現できない困った単位ともいえる。 |
| | | |
| でもこれじゃ不便なので、複数のバイトで1個という単位の変数が生まれた。 | | でもこれじゃ不便なので、複数のバイトで1個という単位の変数が生まれた。 |
| C言語では大体こんな感じ。 | | C言語では大体こんな感じ。 |
| | | |
- | |int型|整数|4バイト|どうも通常は動作している環境って単位みたい。つまり16bitCPUなら16bit、32bitCPUなら32bitとか(Cコンパイラに依存?)| | + | |
- | |float型|小数|4バイト|文字通り少数を扱いたい場合使うらしい| | + | |型名|バイト数|用途|範囲(10進数)|範囲(16進数)|備考|h |
- | |char型| 文字(一文字単位)|1バイト|「A」だの「i」だの| | + | |char型|1バイト|文字(一文字単位)|-127〜127|0x00〜0xFF|「A」だの「i」だの「#」だの| |
- | |double型|少数|8バイト|より桁数が多い少数?| | + | |short型|2バイト|すこし大きな整数|-32768〜32767|0x0000〜0xFFFF|| |
- | |ポインタ型|4バイト|アドレス変数|アドレスを記憶する| | + | |long型|4バイト|かなり大きな整数|-214783648〜214783647|0x00000000〜0xFFFFFFFF|| |
| + | |int型|4バイト|かなり大きな整数|-214783648〜214783647|0x00000000〜0xFFFFFFFF|どうも通常は動作している環境って単位みたい。つまり16bitCPUなら16bit、32bitCPUなら32bitとか(Cコンパイラに依存?)| |
| + | |float型|4バイト|少数を扱える|小数を扱いたい場合使うらしい||| |
| + | |double型|8バイト|より桁数が多い小数|||| |
| + | |ポインタ型|4バイト|アドレス変数|アドレスを記憶する||| |
| | | |
| ・・・こうして見ると、intやfloat、ポインタはみな同じバイト数である。なのでおそらく、実際にメモリ内に記憶されている状態ではまったく同じ状態だと思われる。 | | ・・・こうして見ると、intやfloat、ポインタはみな同じバイト数である。なのでおそらく、実際にメモリ内に記憶されている状態ではまったく同じ状態だと思われる。 |
| float b; | | float b; |
| char *c; | | char *c; |
| + | |
| a=0x00FF00FF; | | a=0x00FF00FF; |
| b=0x00FF00FF; | | b=0x00FF00FF; |
| c=0x00FF00FF; | | c=0x00FF00FF; |
| + | |
| /*この状態で各変数の実際の場所を覗いてみるときっとまったく同じだと思われる*/ | | /*この状態で各変数の実際の場所を覗いてみるときっとまったく同じだと思われる*/ |
| | | |
| float d; | | float d; |
| double e; | | double e; |
| + | |
| aaa = aa + a; /*同じint同士。足し算も代入もOK。*/ | | aaa = aa + a; /*同じint同士。足し算も代入もOK。*/ |
| + | |
| a = b - aa; /*おいおい! bとaaは型が違うだろ同じ4バイトでも!*/ | | a = b - aa; /*おいおい! bとaaは型が違うだろ同じ4バイトでも!*/ |
| + | |
| aaa = e; /*うわ!aaaとeじゃ型が違うしそもそもバイト数も・・・*/ | | aaa = e; /*うわ!aaaとeじゃ型が違うしそもそもバイト数も・・・*/ |
| + | |
| aa = e + d -b; /*もうなにがなんだか・・・*/ | | aa = e + d -b; /*もうなにがなんだか・・・*/ |
| | | |
| int b; | | int b; |
| int c: | | int c: |
| + | |
| a = 18; | | a = 18; |
| b = 38632; | | b = 38632; |
| + | |
| c = a + b; | | c = a + b; |
| | | |
| int b,ab; | | int b,ab; |
| int c: | | int c: |
| + | |
| a = 18; | | a = 18; |
| b = 38632; | | b = 38632; |
| + | |
| ab = (int)a; /*こうすると、char型aの値(18)はそのままに、int型に変換してくれる*/ | | ab = (int)a; /*こうすると、char型aの値(18)はそのままに、int型に変換してくれる*/ |
| + | |
| c = ab + b; /*当然int同士の足し算。そして結果をint型に代入なので、問題なし!*/ | | c = ab + b; /*当然int同士の足し算。そして結果をint型に代入なので、問題なし!*/ |
| | | |
| int b; | | int b; |
| int c: | | int c: |
| + | |
| a = 18; | | a = 18; |
| b = 38632; | | b = 38632; |
| char a; | | char a; |
| int i; | | int i; |
- | | + | |
| + | |
| for(i = 0x0010; i = 0x0014; i = i+1) | | for(i = 0x0010; i = 0x0014; i = i+1) |
| { | | { |
| p = i; | | p = i; |
| printf("データは %d 16進数だと %x だよーん!\n",*p); | | printf("データは %d 16進数だと %x だよーん!\n",*p); |
| + | |
| } | | } |
| | | |
| | | |
| | | |
| + | こんなふうに考えるとわかります。アドレス。文字通り、''住所・番地''です。 |
| | | |
| + | -たとえばアナタの住んでいる所。住所がありますよね?。で、その住所。数字も使われているでしょう? |
| + | -日本国 東京都 葛飾区 亀有 5丁目 18番地 とか。 |
| + | -こういう住所の数字に対して、「なあ?お前の家の住所の三倍ってなに?」ともし聞かれたらどう答えますか? |
| + | -「まずよく寝ろ!話はそれからだ」が正しいですね? <はぁ??? |
| + | -つまり、住所にはたまたま数字が使われていますが、これは倍にしたり割り算したりするようなものではない。つまり、''数字は使っているけど数値ではない''となります。 |
| | | |
| + | 上記の例がエラーになるのはこれが理由です。int(4バイト)だろうがchar(1バイト)だろうが、これらは数値なのです。なので、「住所の番地に数を代入しようとしてるぞ!おかしいぞ!」と、こういうことでエラーになってしまうのです。 |
| | | |
| | | |
| + | そのため、こういう代入をしたい場合は、数値であるiを''型キャスト''して、一時的にアドレス変数タイプに転換してから代入してやる必要があります。 |
| | | |
| + | この場合、何型にキャストしてあげればいいでしょう。pと同じにしてあげればいいのです。変数pはchar型です。しかも、*がついています。char型のアドレス変数(ポインタ)です。なので、 |
| | | |
| + | char *p; |
| + | char a; |
| + | int i; |
| + | |
| + | |
| + | for(i = 0x0010; i = 0x0014; i = i+1) |
| + | { |
| + | p = (char *)i; /* ←ここに注目!*/ |
| + | printf("データは %d 16進数だと %x だよーん!\n",*p); |
| + | |
| + | } |
| | | |
- | ---- | |
| | | |
| + | こうしてあげれば、int変数であるiは一時的にアドレス変数pと同じ型に変化します。よって代入が可能になり、エラーが出なくなります。 |
| | | |
| | | |
| + | もう一度書きますが、アドレス変数(ポインタ)は、たまたま数字を使っているだけの住所番地です。よって、足したり割ったり掛け算したりできる''数値''とはまったく違う型なのです。 |
| | | |
| + | -COLOR(BLUE){ちょっとまて!それはおかしいぞ! この状態で p = p + 1 等の演算をしてもエラーにならないはず。アドレス変数だって計算が出来る''数値''じゃないのか??} |
| | | |
| + | -COLOR(red){大変すばらしい問いです。ほんとですね。アドレス変数であるpに対して、足し算・引き算を行うことはできますしエラーにはなりません。やっぱりアドレス変数も''数値''なのでしょうか???} |
| + | -COLOR(red){では、実験してください。p = p * 3 等、掛け算や割り算をしてみてください。ほらね?エラーになるでしょう??。これはいったいどういうことなのでしょうか?} |
| + | -COLOR(red){難しく考えることじゃありません。たとえば最初に出した、実際の住所・番地で考えてください。} |
| + | --COLOR(red){あなたの住所は「日本国 東京都 葛飾区 亀有 5丁目 18番地」です。この住所に対して、「お前の住所の2倍」というのは成り立ちません。} |
| + | --COLOR(red){しかし、「お前の住所の次」はどうですか?} |
| + | --COLOR(red){「お前の住所の3軒前の家」ならどうでしょうか?。} |
| + | --COLOR(red){そう。成り立ちますね?} |
| + | -COLOR(red){つまり、アドレス変数が足し算引き算はできても掛け算割り算ができないのはこういう理由なのです。そしてこの理屈なら、足し算ができたってやっぱりアドレス変数は''数値''ではないということも矛盾しないでしょう?} |
| + | -COLOR(red){もっと言えば、アドレス変数(ポインタ)を p = p + 2 などとしているのは「2軒後のお家」と言っているだけで、そもそも''足し算ではない''とさえ言えます} |
| | | |
| | | |
| | | |
| | | |
| + | *現実の型キャスト [#x0b20a5e] |
| + | さて、これまで書いたことは、あくまでも概念と理屈の理解のためです。実際のCでは上記そのままではありません。(もちろん理屈そのものは合っているはずです) |
| | | |
| + | **数値の型キャスト [#k48737ef] |
| | | |
| + | たとえばこんな式を考えます。 |
| | | |
| + | char z; |
| + | char c; |
| + | |
| + | z = 0x44; |
| + | c = 0x22; |
| + | |
| + | c = c + z; |
| + | |
| + | printf("data is %x\n",c); |
| | | |
| + | 答えは暗算できますね。結果は0x66になります。もちろんエラーも出ません。同じ型同士の足し算を同じ型に代入したのですから当然ですね。 |
| | | |
| + | ではこんな式はどうでしょう? |
| | | |
| + | int i; |
| + | |
| + | char z; |
| + | char c; |
| + | |
| + | z = 0x44; |
| + | c = 0x22; |
| + | |
| + | i = c + z; |
| + | |
| + | printf("data is %x\n",i); |
| | | |
| + | これまで説明してきた「概念」ではこれはおかしいはずです。だって、変数zと変数cはchar型。しかし代入しているiはint型。型が合わないはずです。''ところが・・・'' |
| | | |
- | ・・・しかし、これでは問題が出てしまいます。そう、 ''p = p + i;''の部分です。 | + | あれれれ〜? これをコンパイルすると、ちゃんと''エラーもなく''、さらに実行してもちゃんと答えが出てきます。どういうこと??? |
| | | |
- | もうお解りですね。これは、 p(ポインタ型) + i(int型) の足し算になります。実際の足し算の値にはなんの問題もないのですが、異なる型同士の演算ということなので問題になるのです。 | + | 実はこれは、厳密には概念通り、型が合わないのです。しかしCコンパイラは、「この程度ならよく使うし、いちいち型キャストの警告出しててもうっとぉしいだろうなぁ。この程度は容認してあげよう」と考えるように調整されているのです。 |
| | | |
- | じゃ、どうするか?。簡単です。 この足し算の時、一時的に変数iをポインタ型にキャスト(型変換)してやればいいのです。 | + | -COLOR(blue){もちろん、 i = (int)c + (int)z; と書いても当然エラーにはなりません。むしろこれが正しいのです。''本来の概念''で言えば。} |
| | | |
- | この変数pはポインタ型。さらに言えば、「char *」型の変数ですね。なので、'' (char *)i''としてやれば、変数pと同じ型になります。よって、 | + | もちろん「許容」にもちゃんと法則があります。 |
| + | ~ |
| + | 許容の法則は一般にちょっとややこしい説明がされていることが多いのですが、こんな風に分けて考えればそうむずかしくありません。 |
| | | |
- | char *p; | |
- | char a; | |
- | int i; | |
| | | |
- | p = 0x0010; | + | まず、左辺と右辺に分けて考えるといいです。 |
| + | -まず右辺内 |
| + | ++混在する一番大きな型に合わせて自動調整される |
| + | ++ただし、int以下のものは全てintに強制変換される |
| + | ++以上の法則で、「とりあえず右辺内」の値が出来上がる |
| + | -次に左辺 |
| + | ++右辺の値や型がなんだろうと左辺の型に強制変換される |
| + | ++もしはみ出た場合は切り捨てられる |
| | | |
- | for(i = 0; i = 4; i++) | + | 基本的にはこれを上から順番に行い、最後に左辺の値が決まると考えてもらってOKです。 |
- | { | + | |
- | p = p + (char *)i; /*これで変数iは一時的にchar *型のポインタ型になる*/ | + | 実際の場合を思考実験してみましょう。たとえばこんな式の場合。 |
- | printf("データは %d 16進数だと %x だよーん!\n",*p); | + | |
- | } | + | char c; |
| + | int i; |
| + | |
| + | c = 0xf3; |
| + | i = 0x12; |
| + | |
| + | c = c + i; |
| + | |
| + | printf("data is 0x%X\n",c); |
| + | |
| + | これは暗算できますね?。答えは0x105になるはずです。しかしこれを実際にコンパイルして実行すると、答えは0x5になります。はて??? |
| + | |
| + | c = c + i; /* ← ここがどう処理されているのか? */ |
| + | |
| + | ++まず右辺。 |
| + | ++おぉ? iはintだけど、cはcharじゃないか!。型が違うぞ! |
| + | ++仕方がない。自動的に許容しよう。 |
| + | ++右辺で一番大きいのは・・・int型(変数i)だな! |
| + | ++よし、全ての変数を無理やりint型として考えよう! |
| + | ++結果、この式は、c = 0x000000f3(int型。4バイト) + 0x00000012 (int型。4バイト)に変換だ! |
| + | ++よし!計算できたぞ!。結果はこうだ! c = 0x00000105(int型。4バイト) だ!。 |
| + | ++これで右辺は終了だ!。今度は左辺への代入処理だ! |
| + | ++左辺への代入の場合、右辺が何だろうと左辺の型に強制変換だ! |
| + | ++むむむ! なんていうことだ! 左辺はchar型。右辺はint型。左辺の型のほうが小さいぞ! |
| + | ++・・・しかし、「右辺が何だろうと左辺の型に強制変換」は決まっている!。これは崩せない! |
| + | ++仕方がない。はみ出す部分は残念だが''切捨て''てしまおう! |
| + | ++右辺をこう切り捨てる。 c = 0x05 (右辺の頭の3バイトが切り捨てられた) |
| + | ++よし! 左辺 c に代入できるぞ。代入! |
| + | |
| + | こういう内部処理が行われて、結果、暗算の結果である0x105は0x05に切り捨てられ、cに代入が完成し、計算が終わるのです。 |
| + | |
| + | |
| + | この、自動型調整はややこしいといわれますが、とにかくポイントは、上記の8番の部分。''「式の処理は、一旦右辺の中で全部解決して、それが終わったら改めて左辺への代入に移る」''というところでしょうか? |
| + | |
| + | -COLOR(blue){少数の場合はどうなるの???} |
| + | -COLOR(red){・・・いや申し訳ない。少数を扱う場合も勉強してみたのですが、どうも整理しきれませんでした。またここはオイラが''OSを作る''ために勉強して整理しているサイトなので、このページとしては少数型はこれ以上はツッこまないことにしようかと・・・} |
| + | |
| + | |
| + | *現実の型キャスト(ポインタ) [#j3516d0b] |
| + | これは「現実」とは言っても、ほぼ概念通りと考えてもらってかまわないと思います。 |
| + | |
| + | アドレス変数(ポインタ)は特別にして唯一無比の型なので、とにかくこれに代入しようとした場合はほぼ''かならず型キャストして代入する''で問題ないでしょう。 |
| + | |
| + | -COLOR(blue){少ない桁数の変数から型キャストした場合どうなるのか?} |
| + | |
| + | こういうのは実験しちゃうのが早いですよ? |
| + | |
| + | int main(void){ |
| + | |
| + | char *p; |
| + | char c; |
| + | |
| + | c = 0x33; |
| + | p = c; |
| + | |
| + | printf("data is 0x%X\n",p); |
| + | |
| + | } |
| + | |
| + | さて、これをコンパイルすると・・・ |
| + | ~おっと!いけない!型キャストを忘れていました。 |
| + | |
| + | int main(void){ |
| + | |
| + | char *p; |
| + | char c; |
| + | |
| + | c = 0x33; |
| + | p = (char *)c; |
| + | |
| + | printf("data is 0x%X\n",p); |
| + | |
| + | } |
| + | |
| + | ・・・実は、これもエラーが出ます。「キャストはいいけどサイズが合わんぞ。サイズくらい合わせろよ!」ってとこでしょうか? |
| + | |
| + | しかし警告エラーは無視してコンパイルし、これを実行すると、答えは0x33と表示されます。おそらくこういう場合、足りないケタ数は0で埋められてしまうようです。 |
| + | |
| + | では、char型の変数の値をアドレス変数に当て込むにはどういう手法が正しいのでしょうか? |
| + | |
| + | 正しい呼び名はわかりませんが、おそらくこういう「二段キャスト」でよいはずです。 |
| | | |
- | こうすることで、型違いの演算という問題は出なくなります。 | + | p = (char *)(int)c; |
| | | |
- | COLOR(red){念のためもう一度言いますが、こういう処理を実際にプログラミングする場合、こんなコードは書きません。単純化するためにこんなことかいてますので。} | + | COLOR(blue){上記。理解のため無理にchar型を使っていますが、やっぱり理想は、せめてint型にしてから、あるいはアドレス変数への代入が予想されるような値は最初からint型で定義して使うというのが本流なのでしょう。きっと。} |