アカウント名:
パスワード:
プログラミング言語Cといえば、ポインタの章が理解できずに何度も読みすぎてそのページだけ黒くなっていました。ご逝去の報に接し、謹んで哀悼の意を表します。
Cの前にマシン語をやっていたので、ポインタは理解できた(というか、ポインタが理解できない人のことが理解できない)のですが、関数へのポインタの配列を返す関数にtypedefして~とか言うと、ついていけなくなります。
それから、
int *i;
は理解できますが、
int* i;
と書く発想が理解できません。「int*」って何だよ?って。
こういう型キャストも大嫌いです。
a = (int *)b;
ここで単項演算子としての「*」の立場がなくなってしまいますから。あるいは、「int」は「*」を修飾している、つまり「*」が主で「int」が従と思えば気分がだいぶ楽になりますけどね。
はっ、もしかして「int* i;」というのは、「* i;」をちょっと修飾したようなものなのでしょうか。(だと「* i,j,k;」とかいうふうに書けちゃうことになってしまうからだめですね。)
パーサを自作すれば、なぜそうなっているのか理解できるんでしょうね。きっと、「*int」と表記することにすると、どこかで破綻するんでしょう。
考え方の1つにすぎませんが,型宣言でも数式解釈と同様に,両辺の型が釣り合っていると思えばわかりやすいかも知れません.
は
int = *i; (この式はコンパイラに通りませんが概念的なものです)
というように両辺の型が釣り合う関係になっていて,アドレス(ポインタ) i が指す先の値にアクセスする演算子 * を付けたもの (*i) は,int 型でなければなりませんよ,という意味です.コンパイラの視点で言うと,代入式を解釈するときの左右の型の一致判定と同じロジックで変数宣言が出来ることになりますし,変数への演算子の付け方が許容されうる限り,それは変数宣言でも同じように使えることになります.
double (*a)[3];
とかでも同様に,左からでなく,最も内側(変数名の部分)の a から外向きに辿っていけば理解しやすいのではないでしょうか.「a はポインタで,その指す先は3要素の配列で,その配列の各要素は double 型」であるということになりますから.
そう考えると,
型名 変数名;
という単純なとらえ方だけでなく,変数名を修飾することが変数宣言でなぜ利用できるのかが理解しやすいかも知れません.また上のような解釈をしているので,私の場合 C++ 流の int* i; のような書き方は気持ち悪くて仕方ないです.
より多くのコメントがこの議論にあるかもしれませんが、JavaScriptが有効ではない環境を使用している場合、クラシックなコメントシステム(D1)に設定を変更する必要があります。
目玉の数さえ十分あれば、どんなバグも深刻ではない -- Eric Raymond
プログラミング言語C (スコア:0)
プログラミング言語Cといえば、ポインタの章が理解できずに何度も読みすぎてそのページだけ黒くなっていました。
ご逝去の報に接し、謹んで哀悼の意を表します。
Re: (スコア:0)
Cの前にマシン語をやっていたので、ポインタは理解できた(というか、ポインタが理解できない人のことが理解できない)
のですが、関数へのポインタの配列を返す関数にtypedefして~とか言うと、ついていけなくなります。
それから、
は理解できますが、
と書く発想が理解できません。「int*」って何だよ?って。
Re: (スコア:1, 興味深い)
pint_t a, b, c;
…わかりやすいでないですか。
と、いうのは全然嘘です。
私もその書き方が気に入ってません。
int* i;
と書かなければ気が済まない人というのは、
「ポインターなのは型だろー、型は型として書かせてくれよ。」
って思ってる人たちなんです。
きっと型名にスペースを含んでいるのが許せない人達なんです。
または、* が型名に含まれていない事が我慢できない人達なんです。
そう思ってる人たちは決して、
int* i, *j, *k;
とは書かない/書けないみたいですね。
そんなに嫌なら、typedef しやがれですよ。> 各位
Re: (スコア:0)
こういう型キャストも大嫌いです。
ここで単項演算子としての「*」の立場がなくなってしまいますから。
あるいは、「int」は「*」を修飾している、つまり「*」が主で「int」が従と
思えば気分がだいぶ楽になりますけどね。
はっ、もしかして「int* i;」というのは、「* i;」をちょっと修飾したような
ものなのでしょうか。(だと「* i,j,k;」とかいうふうに書けちゃうことになって
しまうからだめですね。)
パーサを自作すれば、なぜそうなっているのか理解できるんでしょうね。
きっと、「*int」と表記することにすると、どこかで破綻するんでしょう。
Re:プログラミング言語C (スコア:2)
考え方の1つにすぎませんが,型宣言でも数式解釈と同様に,両辺の型が釣り合っていると思えばわかりやすいかも知れません.
int *i;
は
int = *i; (この式はコンパイラに通りませんが概念的なものです)
というように両辺の型が釣り合う関係になっていて,アドレス(ポインタ) i が指す先の値にアクセスする演算子 * を付けたもの (*i) は,int 型でなければなりませんよ,という意味です.コンパイラの視点で言うと,代入式を解釈するときの左右の型の一致判定と同じロジックで変数宣言が出来ることになりますし,変数への演算子の付け方が許容されうる限り,それは変数宣言でも同じように使えることになります.
double (*a)[3];
とかでも同様に,左からでなく,最も内側(変数名の部分)の a から外向きに辿っていけば理解しやすいのではないでしょうか.
「a はポインタで,その指す先は3要素の配列で,その配列の各要素は double 型」であるということになりますから.
そう考えると,
型名 変数名;
という単純なとらえ方だけでなく,変数名を修飾することが変数宣言でなぜ利用できるのかが理解しやすいかも知れません.
また上のような解釈をしているので,私の場合 C++ 流の int* i; のような書き方は気持ち悪くて仕方ないです.
Re: (スコア:0)
考え方の1つではなくて、そう定義されています。
fがcharの配列へのポインタを返す関数へのポインタの場合、使うときにはこう書くでしょ。
(Pascalだとpointer to function():pointer to array [1..10] of char)
(*(*f)())[5] = 'a';
宣言するときにはtype specifierの後にそのまま左辺をコピーして
char (*(*f)())[10];
キャストするときは、左辺から識別子を抜いて
f = ((*(*)())[5])g;
非常に分かりやすいですね。
分かりにくいのは演算子の優先度がややこしいからだね。
Re:プログラミング言語C (スコア:1)
> ...
> char (*(*f)())[10];
> f = ((*(*)())[5])g;
charが抜けてませんか?
f = (char(*(*)())[5])g;
でもって f がこの型で定義されているなら、
> (*(*f)())[5] = 'a';
は(*)が1つ多いですな。(多い分はOKなので問題ないですが)。
(*f())[5] = 'a';
でよいですよ。(理由は()演算子(関数呼出し演算子)は暗黙のうちのdereferenceをやるから。想像するに、K&Rの時代にfが関数ポインタのときに、(*f)()でなく、f()でOKとしていた仕様に合せたんでしょうね)
ただし、型宣言やキャストのときは暗黙のdereferenceはありませんから(*(*))のように2つ要ります。ご注意を。
多い分にはOKと言うのは、例えば
(*(*(*(*(*(*(*printf)))))))("Hello, world!\n");
とか
(******************printf)("Hello, world!\n");
というのがOKということです。興味のある人はHello worldのプログラムの一部をこれで置き換えてコンパイルしてみると良いでしょう。
> 非常に分かりやすいですね。
間違うくらいだし、あまり分かりやすいとは言えないですね。IOCCC読み慣れている身としては苦はないですが。
実際問題としてなら、私は
struct a { char data[5]; };
struct a *(*f)();
f = (struct a*(*)())g;
f()->data[5] = 'a';
のような感じにしますかね。たぶんこういうことしたい時って、あとから
struct a { char data[5]; int data2[5]; };
みたいに変えたくなるような気がしますし。
# 変数名やタグ名はこんなショボくしません。あくまで例ですので...。
Best regards, でぃーすけ
Re: (スコア:0)
> f = (char(*(*)())[5])g;
おっしゃるとおりです。ありがとうございます。
> でよいですよ。(理由は()演算子(関数呼出し演算子)は暗黙のうちのdereferenceをやるから。想像するに、K&Rの時代にfが関数ポインタのときに、(*f)()でなく、f()でOKとしていた仕様に合せたんでしょうね)
関数はファーストクラスオブジェクトではありませんし、ダイナミックリンクなども考慮したんでしょうか。
私は(*f)()と書くことが多いです。さすがに(*s->f)()は煩雑ですし関数ポインタであるのが明白なので書きませんが。
> > 非常に分かりやすいですね。
> 間違
Re:プログラミング言語C (スコア:1)
なるほど! そういう考えをすればこの変なルールも納得できるかも。しかし、個人的にはK&Rの怠慢 -- 「(*)」を略したい -- をもとにANSIが矛盾しないルールを作った結果、1. 使用時はdereferenceは1個少なくするのが基本 2. 余計なdereferenceは許可、という変なルールが関数ポインタに関してだけ出来たと想像しています。
> 私は(*f)()と書くことが多いです。さすがに(*s->f)()は煩雑ですし関数ポインタであるのが明白なので書きませんが。
全員がそうであればよかった(K&Rと非互換だけどすっきりしたルールを作ってもよかった)のでしょうけど、不幸なことにK&Rと同じく怠慢な私のようなf()としか書かないやつらが蔓延してしまったので現在のルールになったのでしょう。
> ルールが分かりやすいという意味です。人間には厳しいですね。
概ねそうなんですが、関数ポインタだけはルールも変です。すくなくともコンパイラを実装する上では上記を理解していないと駄目です。
> 単項*演算子がPascalの^ように後置であれば、
Cには馴染まないですね。Cでの後置演算子と言えば、++と--の後置バージョンですが、2項演算子と区別するとしてポインタを**の後置とすると、
ポインタのポインタはp** **とか書く羽目になってよろしくないですね。
Best regards, でぃーすけ
Re: (スコア:0)
| struct a { char data[5]; };
中略
| f()->data[5] = 'a';
私の中でエンガチョ警報が鳴り響きました。