基礎プログラミング講座第4回「ポインタ」 (123)

今日はC言語のポインタについての解説です。

注意:この講座はC言語の基礎的な知識を前提としています。

ポインタとは何でしょうか?
ポインタの説明をする前に変数の仕組みを説明していきます

int x=8;
x>>1;

参照渡しと参照の値渡しの違いがよく分からん

変数を宣言すると皆さんのPCのメモリ上に変数を保存するための領域が確保されます。
例えば int という型がありますね。
これはメモリ上に2バイトのサイズの領域を確保するものです。


余談ですが
1バイトは8ビットです。1バイトで表現できる数字は2^8 = 256通りです。
2バイトは16ビットですね。同じように考えると2バイトで表現できる数字は2^16 = 65536です。

intは正負どちらも扱いますから 65536を2で割って 32768です。
0も1つの数字になりますから -32678~32767の数字を扱うことができるわけです。

int で変数を宣言するとメモリ上の2バイトの領域が確保されます。

メモリは1バイトごとにすべて名前がついています。
これを「住所」という意味の英語「アドレス」と呼びます。

言い換えればメモリは1バイトごとにアドレスが付けられているわけです
例えばみなさんのパソコンのメモリが4GBだとします。

4GBは1073741824バイトです。
つまりメモリ上には1~1073741824のアドレスが割り振られています
(実際にはアドレスは16進数で表現されます)

intは2バイトですから4GBのメモリだと1073741824/2 = 536870912個の変数が宣言できるわけです。

>>12
参照渡し:変数は真に別の変数のエイリアスである。よって参照先を変更できない
参照の値渡し:参照の値はあくまでも値である。よって値を書き換えれば参照先を変更できる

>>13
なんでintが2バイトなんだよ

では本題に入って行きましょう

int a;

aという変数を宣言しました。
4000番地と4001番地のメモリが確保されたとします。

a = 0;

この命令により、4000番地と4001番地のメモリに0が代入されました。

>>20
すみません 4バイトでした。


以上が変数宣言、代入によって起きていることの解説です。
次はいよいよポインタの説明に入ります。

>>23
それも正確じゃないな
intは16ビット環境では概ね2バイトであり、32ビット環境では概ね4バイトだが例外も取りうる
使うならint16_tやらint32_tやらのビット数固定型を使うべき

よくこれをまとめ用の記事の為にVIPに投下したな
もう少し考えてやれよ

ポインタ変数は、変数宣言時に * をつけることで宣言できます。

int * p;
int* p;
int *p;

どの場所につけても同じことができますが、この講座では統一して「int *p;」のように変数名の直前につけるようにします。

ポインタ変数は別の変数のアドレスを保持する変数です。
変数のアドレスはどのように表現するのでしょうか?
答えは「変数の前に & をつける」というものです。
&はアドレス演算子といいます。

>>21の例でいくと「&a」と書くことでaのアドレスが表現できます。
ここで注意して欲しいのは「&aのようにしてアドレスを表現した場合、変数の先頭のアドレスが表現できる」ということです。

つまり、 &a と書いた時、これが示すのは「4000番地」ということです。

>>35
転載禁止

x>>=1;だった

>>18
f(&x)は前者でf(p)は後者?
ポインタは値がコピー渡しされるから、実引数の方のポインタは書き換えられないと思うんだけど

では、早速ポインタ変数に変数のアドレスを代入していきましょう

int a;
a = 0;

int *p;
p = &a;

これで a のアドレスが p に代入されました


ここまでこんなことを疑問に思う人がいるかもしれません
「ポインタ変数になんで型がついてるの?」
「アドレス保持するだけなら型なんていらないんじゃないの?」

>>43
仮引数をポインタで受ける事自体が参照の値渡し
ポインタ値をコピーする事自体が「参照の値渡し」であると考えてよい
参照渡しってのはC++の void hoge(int &x); みたいなののこと
xが示す値を変更する事はできない

その疑問はもっともです。
ポインタ変数に代入されるのは「変数の先頭のアドレス」だからわざわざ代入する変数の型に合わせる必要はないように思えます。

実はポインタ変数には2つの機能があります。

①すでに説明してきた「アドレスを保存する機能」
②「保存されているアドレスに保存されている値を表現する機能」

>>44の例を使います。
p = &a;

これによりpは「俺の中には4000番地というアドレスが入ってる」という意味になります。

ポインタ変数の前に「*」をつけると②の機能を使うことができます。
つまり「*p」と書いてやると「俺の中には4000番地というアドレスが入っている。その4000番地に入っている値」という意味になります。

つまり *p は 変数 a と等価の表現になります。

intってレジスタのサイズによるんじゃないのか?

>>56
とも限らない
C言語の規約では、
ASCII文字を全て表現できる最低限のサイズ≦char≦short≦int≦long≦long long
くらいのものだったはず

そしてこの②機能を使うときにポインタ変数の型が生きてきます。
「*p」と書いた時「4000番地に入っている値」となります。
この時ポインタ変数は「自分に保存されているアドレスが指す変数はint型」とわかっていますから「先頭のアドレスから2バイトが変数の内容」、つまり「4000番地と4001番地が変数の内容」とわかるわけです。

ポインタ変数に型を教えてあげないと「*p」でどこまでが変数の内容かわかりません。
char型だったら1バイトですし、doubleだったら8バイトです。
アドレスはあくまで先頭のアドレスですからそこから何バイトをさんしょうするかを知らないと②機能が使えないのです

>>50
OK、多分完全に理解した
仮引数のアドレスを指定するか仮引数の値に入れるかの違いだ
誰かが参照渡されって言ってたのも頷ける

>>65
渡されw
まあ、渡された側が重要って意味では確かにその通りだわ

ポインタ変数に型が必要なことをわかり易く説明するために例を挙げます。
int a;
a = 32000;
int *p;
p = &a;
aが確保するアドレスは4000番地と4001番地です
32000は2進数で「111110100000000」です。1バイトは8ビットですから

4000番地に「11111010」、4001番地に「00000000」が入っているとします。
(厳密に言えばこの順序になるとは限りません。(エンディアン)ここではわかりやすくするためこのように表現します)
pには「4000番地」というアドレスと「int型」という情報を持っています。
つまり「*p」は「4000番地と4001番地の内容」 = 「111110100000000」となる訳です。
型がなければ不具合があります。
例えば勝手にchar型(1バイト)と解釈されれてしまえば「*p」は「4000番地の内容のみ」 = 「11111010」、つまり「250」という全く別の数値になってしまいます。

int a;
a = 32000;
int *p;
p = &a;

printf("pの内容:%d¥n", *p);
printf("aの内容:%d¥n", a);

これは一致します

②機能を使えば変数の内容を書き換えることができます

int a;
a = 32000;
int *p;
p = &a;

*p = 10;

printf("aの内容:%d¥n", a);

結果は「10」になります。

なんかよくわからんけど
*p = &a
とかはできないんか
b=p=&a
とかしたらbにも入るんかね
あと*p=aっていうのはありなんかね
まったくの初心者なので教えてちょ

ポインタの使用例として「値渡し」「参照渡し」を説明していきたいと思います

ポインタとアドレスは同じだと思ってたわwww
ポインタを納める変数をポインタ変数いうんだと思ってたわwww

>>75
間違ってはいない
ただ、ポインタ変数には型情報がついてるだけだ

>>73
*p = &a

これはそもそも型が違います
*pはpに保存されているアドレスに保存されている変数 = int型
&p はアドレスです

*p=a

ありです。
これが示す内容は「pに保存されているアドレスに保存されている変数にaを代入する」ということです。

int a = 0;
int b = 1;
int *p;
printf("&d¥n", b); //これは「1」が表示されます
p = &b;
*p = a;
printf("&d¥n", b); //これは「0」が表示されます

ポインタよりもスレッド教えてください!

複数のスレッドの完了を監視して処理を挿みたいんですが

複数のスレッドのそれぞれが終了する毎に処理を割り込ませるには
joinで待機すると
監視するスレッドと同数のスレッドを立ち上げなければいけないので
スレッド作成のオーバーヘッドが大きくなるので嫌なんですが
1つのスレッドにて複数のスレッドの生死を効率よく監視する方法って
あるんですか!?

ちなみにC#.NETでお願いします。

>>78のコード訂正

int a = 0;
int b = 1;
int *p;
printf("%d¥n", b); //これは「1」が表示されます
p = &b;
*p = a;
printf("%d¥n", b); //これは「0」が表示されます

>>79
async/awaitを使えばそんなの気にせずに全部かけるよ!

宣言の「*」とアドレス先の中身を指す「*」は違うという事は
説明しておいたほうがいいんじゃないかなぁ

>>75
アドレス型の変数がポインタ

何かの本で読んだけど、一応仕様としてはポインタは必ずしもアドレスである必要はないらしい

>>79
なお、単純にオーバーヘッドを消したいだけならば、スレッドプールを使えばよい
BackgroundWorkerあたりなら単純

>>78
int a;
int* p = &a; //可能

int a;
int* p;
*p = a; //不可能

>>82
ContextBoundObjectのメソッドをトレースするためにRealProxyかいてるんだけど
監視対象のスレッドがばらばらでDebug.Printだと順番が入り乱れるから
じゃあスレッド毎に分けて監視してやろう→じゃあスレッドが終わったらまとめてトレース結果出力しよう
っていう意図なので、ContextBoundObjectの透過プロキシ側で呼出元に意識させずに呼出元スレッドを監視したいんです!

>>87
そんなもんにオーバーヘッドとか何とか気にしてどうすんだよ!

そんなにスレッド数だけ減らしたいなら、Dictionaryに監視対象スレッドとトレース用クラスを全部ぶち込んどいて、
監視用スレッドは100ミリ秒とかごとに起きてスレッドの状態を確認して、
スレッドが終了してたら出力の後Dictionaryから外す、とかでもいいだろう

>>91
やっぱりそういう地味な方法しかないのかね。
なんかかっこいいのがいいんだけど

>>93
喜べ! WaitForMultipleObjects APIなんてものもあるぞ!
全て終わるのを待つこともできるし、1つだけ終わるのを待つこともできる
C#で同様の事ができるかどうかは知らん

C一通り勉強して今C++やってるんだが
Cはポインタも言うほど難しくなかったけど、C++はクラス関連の覚えることが多すぎて頭パンクしそう

調べたら、WaitForMultipleObjects() の .NET 版として、WaitHandle.WaitAny() ってのがあった
が、Thread は WaitHandle を継承していないから使えん……!
どうやらスレッドでやるにはやはり WaitForMultipleObjects() を使うしかなかったようだ

続きマダー?

いや……ちょっと待てよ
逆に WaitHandle を継承して Thread 用 WaitHandle クラスを作る事もできるのではないだろうか
それができるなら、むしろ汎用性という点ではその方がより好ましいと考えられる

>>96
構造体に関数もてるようになったのがクラスだ
それとスコープ覚えればだいたいわかった気になれる

めんどくせえのはデザインパターンとかそのへん

クラスよりテンプレートのほうがよっぽど意味不明だと思う
C++はSFINAEとか下らない機能作ってないでstatic if作れよ

>>104
親クラス型に子クラス型の変数を入れられるとか
virtualの付かない遮蔽定義は多分使うことがないから気にしなくていいとか
着々と理解してはいるはず

>>105
テンプレートはかなり便利だと思った

static if って静的なifか
#if とは違うのかな

>>108
>親クラス型に子クラス型の変数を入れられる
間違い。親クラス型の参照やポインタに、子クラス型の変数の参照やポインタを入れられる、だ
間違って

Derived d;
Base b = d;

なんてやった日には、単純にdの中のBase部分がbにコピーされるだけだから絶対に間違えるなよ

>>111
#if はプリプロセッサ命令なので、条件文は文脈を考慮しない
static if はコンパイラが解析する構文なので、C++として有効な式で評価できる
例えばこう

template <class T>
struct Hoge
{
 static if (sizeof(T) == sizeof(int)) {
  void piyo() {} // Tがintと同サイズの場合のみpiyo()が定義される
 }
};

>>113
thxそうだったかも

いわゆるBigInteger系のクラスを使う

119
言語サポートも標準ライブラリサポートもされていない
まあ、構造体と操作用関数群からなるライブラリは作れる

このSSまとめへのコメント

このSSまとめにはまだコメントがありません

名前:
コメント:


未完結のSSにコメントをする時は、まだSSの更新がある可能性を考慮してコメントしてください

ScrollBottom