C ポインタ 新しいページはコチラ
提供: yonewiki
(→ポインタ) |
(→ポインタ) |
||
1行: | 1行: | ||
+ | [[C PlusPlus#Cにもあった技術|C++]]へ戻る | ||
+ | |||
+ | |||
※このページではC言語にも存在していたという意味で記事タイトルがC ポインタになっていますが、<br /> | ※このページではC言語にも存在していたという意味で記事タイトルがC ポインタになっていますが、<br /> | ||
[[C PlusPlus|C++]]でも同様です。[[C PlusPlus|C++]]だけの機能がある場合は明記します。<br /> | [[C PlusPlus|C++]]でも同様です。[[C PlusPlus|C++]]だけの機能がある場合は明記します。<br /> | ||
<br /> | <br /> | ||
== '''ポインタ''' == | == '''ポインタ''' == | ||
− | + | ポインタ技術は敷き詰めると1冊の本が描けるくらい、深い技術です。ここでは、基本的なことを説明しています。ポインタのより深い理解のためのポインタ応用の記事は別途作成の予定です。 | |
+ | |||
+ | |||
+ | プログラムを組む上で、各種の型の変数を使いますが、変数自身はコンピュータ上のメモリ空間に保存されます。 | ||
ポインタはそのメモリ空間のアドレスを保持するための変数です。int型変数のためのint型のアドレス変数みたいなもの<br /> | ポインタはそのメモリ空間のアドレスを保持するための変数です。int型変数のためのint型のアドレス変数みたいなもの<br /> | ||
− | + | ここで少しメモリ空間についてのイメージを少しだけもってもらえるために、補足しておきます。<br /> | |
− | + | メモリ空間にはアドレス番号と呼んでいるものが割り振られています。日本の空間の地図上のどこにあなたの家があるかを指し示す住所と同じようなものを、単なる番号だけで定義しているのです。半導体メモリDRAMについて少し調べると長方形上の敷地3cm*4cm位の中に4~8つほどの長方形の敷地があって、全部で4G個(個って書きましたが、情報処理用語としてはbit(ビット)という単位が使われます。Gはギガで10の9乗で10億)ほどの記憶領域があることがわかります。<br /> | |
− | + | 更にその3cm~4cmの敷地を同じ樹脂の中に複数枚重ねたり、その樹脂の塊を電子回路基盤に1個~16個くらいとりつけていたり、その電子回路基板を複数個とりつけたりして、現在のNotePCとかでは8GByte(8bitで1Byte(バイト))という容量のものを取り付けて販売している状況です。ある人はプログラム的には一本に並んだ数十億の箱だと言ったりしますが、物理的には、DRAMメモリのある敷地の中のある一点だったり、HDD上においやられた領域だったりするわけです。<br /> | |
− | + | ※単位についてですが、K(キロ)は普通10の3乗で1000ですが、厳密にはコンピュータでは2の10乗の1024Byteが1KByteになります。2の10乗KByteの1024KByteで1MByte(メガバイト)ですし、1024MByteで1GByteとなります。但し、普通に1000Byteを1KByteとしたり、1000KByteを1MByte、1000MByteを1GByteとしている。ざっくりとした仕様でコンピュータの性能を表現しているものが一般的です。情報処理用語として1TByte(テラバイト テラは10の12乗)と表現した場合、1兆0995億1162万7776Byteですが、ざっくり仕様が使われている各種メーカの容量では、実際には10の12乗Byteで995億1162万7776Byte少ないです。仕様書の隅っこに1000kByteを1Byteとして計算していますとか明示してあります。<br /> | |
− | + | CPU(Central Processing Unit)とよばれる中央処理装置にも記憶領域があります。キャッシュとよばれています。HDD(Hard Disk Drive)やSSD(Solid State Drive)にも処理記憶領域は作れます。いろいろなところに作れますがアドレスは番号だけで管理されています。CPUの中にある記憶領域を使った場合が最も早く処理され、次にDRAM、そしてSSD、HDD、その他という具合に保存されている場所によっても処理速度が異なるのも特徴です。記憶している領域により処理速度が低下する問題を解決する手法もよく使われる命令を処理速度の高いところにおいたりと、いろいろな試みがなされています。いろいろな試みについてはWindowsやUnixといったOS(Operating System)側が制御します。<br /> | |
− | + | 少し、脱線しましたが、記憶領域にはアドレス番号が割り振られているということでした。また更に脱線すると、32bitOSとか64bitOSという種類がありますが、<br /> | |
このアドレス番号の割り振りに使われる桁数に違いがあります。32bitなら2の32乗個のアドレス42億9496万7296個、64bitなら2の64乗個のアドレス1844京6744兆0737億0955万1616個が使えるということになります。 | このアドレス番号の割り振りに使われる桁数に違いがあります。32bitなら2の32乗個のアドレス42億9496万7296個、64bitなら2の64乗個のアドレス1844京6744兆0737億0955万1616個が使えるということになります。 | ||
1Gは10億7374万1824Byteでしたから、40億ちょいっつうのは4GByteのことで、32bitOSでは4GByteまでしか使えないということになります。<br /> | 1Gは10億7374万1824Byteでしたから、40億ちょいっつうのは4GByteのことで、32bitOSでは4GByteまでしか使えないということになります。<br /> | ||
− | + | という簡単な基礎知識を頭の片隅に本題に入ります。<br /> | |
− | + | 例えば、Visual Studio2012ではint型は4Byteの変数で使える値の種類は2の4*8bit=32bitで2の32乗=42億9496万7296となります。実際には負の数も表現できますので、先頭1ビットは正負の記号を表し、0なら正、1なら負数で全部32bit全部が1のときが-1です。2進数の補数ってのを勉強しないとダメかも、負数の最大は100000…0000001で、-21億4748万3646~21億4748万3647が表現できる値です。その4Byteを収めてるアドレスは64Bitアドレスなら1844京…の中のどこかから4Byte分のアドレスを使ってるということです。全部のアドレス分の資源16EByte(Eはエクサで10の18乗で100京)がコンピュータに備わっていればの話ですが... | |
とにかく、そういったアドレスを覚えさせるのが、ポインタ変数です。<br /> | とにかく、そういったアドレスを覚えさせるのが、ポインタ変数です。<br /> | ||
34行: | 40行: | ||
で定義(Definition)できます。<br /> | で定義(Definition)できます。<br /> | ||
例1:定義<br /> | 例1:定義<br /> | ||
− | < | + | <syntaxhighlight2 lang="cpp" line start="1"> |
#include <iostream> | #include <iostream> | ||
int main() { | int main() { | ||
41行: | 47行: | ||
return 0; | return 0; | ||
} | } | ||
− | </ | + | </syntaxhighlight2> |
<br /> | <br /> | ||
ポインタのややこしいところは、ポインタ変数を使って代入するときも同じく*を使う事にあるのかもしれません。定義と混同しやすいです。<br /> | ポインタのややこしいところは、ポインタ変数を使って代入するときも同じく*を使う事にあるのかもしれません。定義と混同しやすいです。<br /> | ||
例2:ポインタ変数での代入<br /> | 例2:ポインタ変数での代入<br /> | ||
− | < | + | <syntaxhighlight2 lang="cpp" line start="1"> |
#include <iostream> | #include <iostream> | ||
int main() { | int main() { | ||
57行: | 63行: | ||
return 0; | return 0; | ||
} | } | ||
− | </ | + | </syntaxhighlight2> |
実行結果※サンプルの動かし方は[[Cpp クラス]]の一番下に記述があります。 | 実行結果※サンプルの動かし方は[[Cpp クラス]]の一番下に記述があります。 | ||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
72行: | 78行: | ||
そして、アドレスそのものを使っての変数同士のやりとりは以下の例ように記述します。<br /> | そして、アドレスそのものを使っての変数同士のやりとりは以下の例ように記述します。<br /> | ||
例3:ポインタ変数で実際にアドレスを使う手法<br /> | 例3:ポインタ変数で実際にアドレスを使う手法<br /> | ||
− | < | + | <syntaxhighlight2 lang="cpp" line start="1"> |
#include <iostream> | #include <iostream> | ||
int main() { | int main() { | ||
85行: | 91行: | ||
return 0; | return 0; | ||
} | } | ||
− | </ | + | </syntaxhighlight2> |
実行結果 | 実行結果 | ||
− | < | + | <syntaxhighlight2 lang="cpp"> |
intNum=50 | intNum=50 | ||
*pintNum=50 | *pintNum=50 | ||
pintNum=00000000001BFC34 //ここの結果はプログラムの書き方を変えたり、プロジェクト設定環境が違うと異なる数字になります。64bit設定にするとアドレスの桁が数が増えるのは確か。 | pintNum=00000000001BFC34 //ここの結果はプログラムの書き方を変えたり、プロジェクト設定環境が違うと異なる数字になります。64bit設定にするとアドレスの桁が数が増えるのは確か。 | ||
sizeof pintNum=8 //ここの結果はプログラムプロジェクト設定や環境が違うと異なる数字になります。 | sizeof pintNum=8 //ここの結果はプログラムプロジェクト設定や環境が違うと異なる数字になります。 | ||
− | </ | + | </syntaxhighlight2> |
上記のように変数名の前に&をつけるとアドレスを返してくれる。このときの&をアドレス演算子と呼んでいます。したがってもともとアドレス変数であるpintNumに対しても&アドレス演算子をつけることができ、アドレス番号を格納しているアドレスが返ります。プログラム上ではポインタ変数はint型の変数と同じアドレスを見ることで、名前は違っても同じものを指し示すことができるというのが上記の例です。アドレス番号っていったのに数字じゃないのが返って来てるよ?って言う方は16進数や2進数を勉強する必要があります。16進数での表示がされています。<br /> | 上記のように変数名の前に&をつけるとアドレスを返してくれる。このときの&をアドレス演算子と呼んでいます。したがってもともとアドレス変数であるpintNumに対しても&アドレス演算子をつけることができ、アドレス番号を格納しているアドレスが返ります。プログラム上ではポインタ変数はint型の変数と同じアドレスを見ることで、名前は違っても同じものを指し示すことができるというのが上記の例です。アドレス番号っていったのに数字じゃないのが返って来てるよ?って言う方は16進数や2進数を勉強する必要があります。16進数での表示がされています。<br /> | ||
104行: | 110行: | ||
ポインタが配列になる場合の例 | ポインタが配列になる場合の例 | ||
− | < | + | <syntaxhighlight2 lang="cpp"> |
int pnIntArr[3] = {100,110,120}; | int pnIntArr[3] = {100,110,120}; | ||
int* pnIntArrPos; | int* pnIntArrPos; | ||
133行: | 139行: | ||
printf("★pnIntArr配列変数出力の指定間違い\n"); | printf("★pnIntArr配列変数出力の指定間違い\n"); | ||
printf("(pnIntArr) + 2 :%d Miss!\n",*pnIntArr + 2); | printf("(pnIntArr) + 2 :%d Miss!\n",*pnIntArr + 2); | ||
− | </ | + | </syntaxhighlight2> |
出力結果 | 出力結果 | ||
− | < | + | <syntaxhighlight2 lang="text"> |
pnIntArrPosポインタ変数出力(配列操作) | pnIntArrPosポインタ変数出力(配列操作) | ||
pnIntArrPos + 0 :100 | pnIntArrPos + 0 :100 | ||
148行: | 154行: | ||
★pnIntArr配列変数出力の指定間違い | ★pnIntArr配列変数出力の指定間違い | ||
(pnIntArr) + 2 :102 Miss! | (pnIntArr) + 2 :102 Miss! | ||
− | </ | + | </syntaxhighlight2> |
最初に配列を勉強すると、このようなポインタの表現が理解しづらいものに感じられますが、配列の[]を使った表現はポインタ変数の特殊な表記方法と思った方がいいのかもしれません。[]で配列を定義するとアドレス変数としての意識が薄れ、配列変数がポインタの役割をもっていることを忘れてしまいそうになります。例えば int a[1] = {200 , 210};のように定義するとaというint型の変数を定義したかのように錯覚してしまって混乱するのだと思います。それで、printf("%d", a);で要素0番目の出力をしようとしたりする間違いをしてしまうだろうなぁと思う。[] を使った配列宣言は初期値が設定できるのと、要素番号でメモリの確保ができるのが利点です。ポインタが配列変数であるためには初期化された配列のアドレスを上記の例のように受け取るか、new演算子やmalloc関数で動的にメモリを確保し生成する必要があります。 | 最初に配列を勉強すると、このようなポインタの表現が理解しづらいものに感じられますが、配列の[]を使った表現はポインタ変数の特殊な表記方法と思った方がいいのかもしれません。[]で配列を定義するとアドレス変数としての意識が薄れ、配列変数がポインタの役割をもっていることを忘れてしまいそうになります。例えば int a[1] = {200 , 210};のように定義するとaというint型の変数を定義したかのように錯覚してしまって混乱するのだと思います。それで、printf("%d", a);で要素0番目の出力をしようとしたりする間違いをしてしまうだろうなぁと思う。[] を使った配列宣言は初期値が設定できるのと、要素番号でメモリの確保ができるのが利点です。ポインタが配列変数であるためには初期化された配列のアドレスを上記の例のように受け取るか、new演算子やmalloc関数で動的にメモリを確保し生成する必要があります。 | ||
− | < | + | <syntaxhighlight2 lang="cpp"> |
char* pcStrNew; | char* pcStrNew; | ||
pcStrNew = new char[256]; | pcStrNew = new char[256]; | ||
… | … | ||
delete[] pcStrNew; | delete[] pcStrNew; | ||
− | </ | + | </syntaxhighlight2> |
ポインタ変数は所詮アドレスを保持しているだけの32bitアプリなら4byteの固定長の変数です。プログラムの中で配列変数を指し示すようになるって感じですが、アドレスのインクリメントとかで、メモリを確保していないアドレスとかに行くとプログラムは不正な処理をしたことになりますので注意が必要です。ポインタはどこへでもいけるけど、知らない場所にいくとすぐ検挙されます。悪さをしちゃいけないです。 | ポインタ変数は所詮アドレスを保持しているだけの32bitアプリなら4byteの固定長の変数です。プログラムの中で配列変数を指し示すようになるって感じですが、アドレスのインクリメントとかで、メモリを確保していないアドレスとかに行くとプログラムは不正な処理をしたことになりますので注意が必要です。ポインタはどこへでもいけるけど、知らない場所にいくとすぐ検挙されます。悪さをしちゃいけないです。 | ||
176行: | 182行: | ||
[[C 文字列配列]]※2次元配列<br /> | [[C 文字列配列]]※2次元配列<br /> | ||
<br /> | <br /> | ||
− | [[C PlusPlus|C++]]へ戻る | + | |
+ | |||
+ | [[C PlusPlus#Cにもあった技術|C++]]へ戻る |