アカウント名:
パスワード:
プログラミング言語Cといえば、ポインタの章が理解できずに何度も読みすぎてそのページだけ黒くなっていました。ご逝去の報に接し、謹んで哀悼の意を表します。
私自身はPascalとModula-2とFortranの経験しかない段階でVer.2.1のWindowsからWindowsのプログラミング本だけを片手にイキナリC言語に入ってしまったのでメモリはリニアじゃなくて切れ切れだわ、ポインタじゃなくてハンドル(実体はポインタへのポインタだけど使用前にはロック必須)だわ、標準ライブラリは使えないわ、とひどい目にあいましたw(とはいえ仕事じゃなくて学生が趣味でやってただけなので、ひどい目といっても知れてましたが。)
K&Rはそのずっと後、卒論・修論でC++をさんざん使った後に古典として読んだので、世代的には「K&Rでプログラミングを覚えたぜ」世代の範囲内なのにこのトピックではあまり熱く語れないという…。
Cの前にマシン語をやっていたので、ポインタは理解できた(というか、ポインタが理解できない人のことが理解できない)のですが、関数へのポインタの配列を返す関数にtypedefして~とか言うと、ついていけなくなります。
それから、
int *i;
は理解できますが、
int* i;
と書く発想が理解できません。「int*」って何だよ?って。
Cの前に、マシン語をやっていたので多分同じようなもんだと思いますが、
は私にはしっくりきましたよ。
Javaやってる人は
int[] i; // 配列型のiという変数
という書き方をするので、同意してもらえるかも。変数名とその振る舞いは分離しておきたい、というだけです。
/* なんにせよ、ポインタ変数なのか、普通の変数なのかがよく分かるように * 命名していれば混乱の問題は無いと思うんですが、 * a,b,i,j,kで使われる例ばっかりなのがよくないのかなぁ、と・・・ */
int* i;int[] i;
くらいなら、そういうものだと天下り式に覚えてしまってもいいのですが、それだと、応用が利きません。たとえば、関数ポインタ [sakura.ne.jp]というページには、
void (*po[])() = { kitty , sakura , hina };
という例が載っていますが、この(*po[])()という部分について、演算子の優先順位の低い部分から順番に剥ぎ取っていって、
>void (*po[])()をint* (*po[])()としてみると、・(*po[])() は、int*である以下、void→int*で置換して終了。
うむ、応用が効きそうだ、とか言ってみる。
この場合逆にint *(*po[])()と書いたら最初の「*」がどちらに付くか迷いそうな気もしないでもない。
けどint (*p)[5];のような場合は int* だと書けないっぽいね。
#void func(int **p); int a[5][10]; func(a);#の間違いが分からなければもう一度プログラミング言語Cをやり直し!
> int* (*po[])()> としてみると、> ・(*po[])() は、int*である
「po」の直前の「*」を、「int*」のように型宣言の側に出す方法はありますか?そうでないと、変数名の側に「*」を書く文法の必要性が残ってしまいますので、それなら単純なポインタも「int *i;」でいいじゃん、ということになってしまいます。
同じような例だけど、int *p[];はint* p[];と書き直せるけど、int (*p)[];は、どのように書き直したらいい?
> この場合逆に> int *(*po[])()> と書いたら最初の「*」がどちらに付くか迷いそうな気もしないでもない。
それは、演算子の優先順位を理解してないからでしょう。Cの原則として、スペースの位置で意味が変わってはいけないのでは。
> 「po」の直前の「*」を、「int*」のように型宣言の側に出す方法はありますか?
typedef int* (po_func_t)(void);
po_func_t* hoge[] = {kitty , sakura , hina};
外にでました!
> int (*p)[];> は、どのように書き直したらいい?
typedef int int_array_t[];int_array_t* p;
で行けます!
…とか、そういう話ではないのは理解はしているが、基本的にどちらの構文も同じように正常にコンパイルできるからには、「どっちに * をつけたっていい。」ってのが正しい事だと思う。私は typ
普通のプログラマはそんなトリッキーなことを考えずに、コンパイラの最適化に任せてだらだらと冗長に書けばいいんだと思います。
C#とDはint* i,j,kがCのint *i, *j, *kになるように改良されていますね。配列も同様にint[] arrayと[]を型側に書くようになっている。Cという文脈で話す限りint *iという解釈をするほうが落とし穴に落ちにくいのは確かですが、やはりそれはCの構文上の失敗を解釈によってフォローしているだけで、型に含められるほうが自然だと思います。
> でも、ご多分に漏れず int* i, j; て書いてバグってから、
型をどう見なすか?と、Parse時の結合則がどうなってるか?(つまり文法)とは別の問題です。
そう見ることが出来ていれば、上記のような間違いも起こさないし、そういうソースに出会っても理由がすぐに判ります。
…というキッチリした視点を私に叩きこんでくれたのがあのC本でした。
あの本が無ければ私は「自在にプログラミングする」能力など到底持てず、Cを(他の言語でも)ちょいと書いてはバグって理由が判らずウダウダするばかりの三流似非プログラマにしかなれなかったことでしょう。(それ以前にプログラマ/趣味グラマになってなかったかも知れないが)
早い話が我が人生最大の恩人というか恩本がアレです。
ひたすら冥福をお祈りいたします。
そうなんですけど、それがわかりにくいんですよそのわかりにくさは御大も認識しているわけで
C の宣言の構文,とくに関数へのポインタを含む部分は酷評を受けることがある。この構文は宣言と実際の使用を一致させるように作られている。これは単純な場合には問題ないが,難しいものは混乱のもとになる。その理由は,宣言は左から右へと読むことができず,またカッコを使いすぎる面があるからである。(K&R 第2版 石田訳 p.148)
こういう型キャストも大嫌いです。
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; のような書き方は気持ち悪くて仕方ないです.
今は後者が主流な気がする本の影響もあってC++使いは特に
私の場合、Z80世代からパソコンをはじめて、今の本職はC++ですが、
int *p;
の書き方じゃないと落ち着きません。その変数がポインタであることが見た瞬間に理解できるような書き方をしたいので、*と変数名はつなげて書きたいです。
初期のCを飛ばして、C++やJavaから入った人だと、
int* p;
と書くようです。
そういう人は、 unsigned char* p; とか書くんでしょうか?unsignedとcharの間にスペースが入っているのに、charと*の間が詰まってるのって、気持ち悪くないですかね?
こういうのを見る度に、こっそり書き直したくてそわそわします。しかし、バージョン管理システムに履歴が残ってしまうので、迂闊に書き直すわけにもいきません。まったく困ったものです。
以下の C++ における char* p; なコードは、char *p; 派としてはどのように書くと一番しっくり来ますか?
const char* const str = "ABC"; // 当然 const char const *str とは書けません
C++ は char *p; より char* p; だと思いますよ。
私は個人的にconstは後ろ寄りに書く習慣でやっていますので、
const char *p; ではなく、 char const *p; と書きます。
従って、 const char* const str = "ABC"; は、 char const *const str = "ABC"; になります。
まあ「*const」の部分はちょっと微妙なので、間にスペースを入れても入れなくてもいいかなと思います。
そんなわけで、やっぱり C++ でも char* p; より char *p; だと思いますよ。
"const str" に * を付ける、という感じですか。なるほど。 しかし個人的には *const はかなり座りが悪い感じがするので、それならいっそ char * p; でいいようにも思えますね。
「pはポインタである」が真っ先に関連づけられることから、*pと書くのが理にかなっていると思うのですが。
話はそれますが、WindowsのAPIでよく現れるような、LPDWORD p;の様な、わざとポインタを隠蔽するような書き方は、愚の骨頂だと思うです。
いっそのこと、複数の変数をまとめて宣言できるってのを廃止するってのは?int a;char b;int* c;char* d;としか書けなくすれば万事解決です。
別にポインタを特別視はしていません。
「pはポインタである」→ *p「*pはintである」→ int *p
と読み下せます。(と多くの方が同様な主張をされていると思います)
同じ理屈で、
「funcはポインタである」→ *func「*funcは関数である」→ (*func)()「(*func)()はintを引数にとる」→ (*func)(int)「(*func)(int)は結果を返さない」→ void (*func)(int)故に、funcはintを引数にとり、結果を返さない、関数へのポインタである。
のように自然に読み下せます。
だから、*と変数名はつながっていないと、様々な型宣言で破綻します。
C言語の構文解析上は「int *p」も「int* p」も同じですから、どっちで書くかは、文法の話ではなく「コーディングスタイル」の問題じゃないですかね。まあ、スタイルの問題だからこそ、どちらも派閥も譲れないんだと思いますが
私は「int *p」と書く方ですが、そう書くようになったきっかけは「K&R で int *p と書かれていたから」です。ちゃんと文法を知ってからは、「ポインタの * は右結合なので、*pとくっつける」と、スタイルの意味は理解しましたが、それは後付けです。そういう構文解析が行われるからといって、「int* p」と書くことが文法違反になるわけじゃありませんし。
その後、 Stroustrup を読んで「int* p」と書く流儀の存在を知りましたが、あまりなじめず、C++でも「int *p」と書くのを貫いてます。
#「ifやwhileなどの予約語の後(の間にはスペースを入れる」「関数とその後の(の間にはスペースは入れない」#「ifやwhileと同じ行の終わりに { を入れる」など、基本的なコーディングスタイルはK&Rで通してるんですが、#インデントが5タブなのだけは、さすがに真似できなかった…
>なぜポインタ型だけ特別視するのでしょう?
アセンブラ寄りの人にとってはポインタ型という概念自体が気持ち悪いからじゃね?
>アセンブラ寄りの人にとってはポインタ型という概念自体が気持ち悪いからじゃね?
それは86系に限っての話じゃないですかね。
char *p, *p2ならそう書きますchar *pの場合は目が疲れてぼやけた状態で見るとcharと見間違えるときがありますまたchar* pのほうがなんとなく打ちやすいので変数1つの時はchar* pって書いてます
Z80アセンブリは判りますが、C言語はポインタで挫折ポインタをZ80のニモニック的に教えてください...........orz
レジスタに入っている数値をアドレスとみなして、メモリにアクセスすることです。アセンブラやってる人なら、ごくふつうにやってることです。
LD HL,なんとかかんとかLD A,(HL)
というのが、
char *hl;char a;hl = なんとかかんとか;a = *hl;
というのに相当します。アセンブラには型がないので、数値をアドレスとみなすのは人間の勝手ですが、Cだとアドレス専用に使う変数があって、それがポインタです。
「深い」って「判りにくい」の別名だと思うので、あんまりプログラミング言語に積極的に求められる性質ではなく、肯定的に捉えられる性質でもないんじゃないかなと思います。特に、その時代の技術では代替の効きにくい重要必須なコンセプトやメカニズムならともかく、単なる構文的な判りにくさにはあまり入れ込むのはどうかなーと。ポインタ型の観念や機構は重要だろうけど構文はそれに比べてそれほど重要でないんじゃないかと思います。
それでもCぐらいだと構文の瑕疵まで含めて完全に理解しようと思えばできなくはないのでしょうが、C++あたり使ってると「ギリギリまで使い切れる完全な理解に基づいて書く」ではなくて「より限定的だけど読みやすく、瑕疵には触れず安全目な理解の範囲で書く」へと段々割り切りがw(で、ギリギリのことをしなければならない羽目に陥ったらそのときはあきらめてリファレンスを読み直す。)まぁ、「C実践プログラミング」本とか「MISRA C」規約的な方向性ですね。(とはいえ私個人はそこまでストイックではないですが。)
「pはcharのポインタ」と教えようとするからわかりにくくなるらしいですね。「C言語では宣言時には使うときと同じように書く」のが基本なので、char *p;と書いて「*pはchar」と教えればいいらしいです。char *p, q;でも「*pとqはchar」と、ごく自然に教えられます。
>関数へのポインタの配列を返す関数にtypedefして~とか言うと、ついていけなくなります。
ジャンプテーブルですよね?普通にマシン語でよく使う手だと思うんですけど・・・
carとcdrが理解できずにポインタの道に進んだ人も多いのではないでしょうか?
「プログラミング言語Cには誤訳が多いせいで余計に難解になっている」って話、ありませんでしたっけ?
あれ読んで理解出来る人は偉いと思います。
そのせいで読めなかったとは言いませんが、結局「現実的なCプログラミング」を読むまで、殆ど理解出来ませんでした。
より多くのコメントがこの議論にあるかもしれませんが、JavaScriptが有効ではない環境を使用している場合、クラシックなコメントシステム(D1)に設定を変更する必要があります。
にわかな奴ほど語りたがる -- あるハッカー
プログラミング言語C (スコア:0)
プログラミング言語Cといえば、ポインタの章が理解できずに何度も読みすぎてそのページだけ黒くなっていました。
ご逝去の報に接し、謹んで哀悼の意を表します。
Re:プログラミング言語C (スコア:2)
借りっぱなしで2版が出る頃には同じように黒ずんでました。
当時は(今も?)訳が読みづらいと評判でしたが、別に苦にならなかったなあ。
あれがなければこっちの世界に踏み込む事はなかっただろうと思います。
ありがとうございました。
Re: (スコア:0)
この教科書でC言語嫌いになった人も少なくないと思う。
でもかなりお世話になりました。アンサーブックにも(笑
Re:プログラミング言語C (スコア:1)
ありませんでした。謹んで先生のご冥福をお祈りいたします。
同世代的経験か・・・ (スコア:1)
私自身はPascalとModula-2とFortranの経験しかない段階で
Ver.2.1のWindowsからWindowsのプログラミング本だけを片手にイキナリC言語に入ってしまったので
メモリはリニアじゃなくて切れ切れだわ、ポインタじゃなくてハンドル(実体はポインタへのポインタだけど使用前にはロック必須)だわ、標準ライブラリは使えないわ、とひどい目にあいましたw
(とはいえ仕事じゃなくて学生が趣味でやってただけなので、ひどい目といっても知れてましたが。)
K&Rはそのずっと後、卒論・修論でC++をさんざん使った後に古典として読んだので、
世代的には「K&Rでプログラミングを覚えたぜ」世代の範囲内なのにこのトピックではあまり熱く語れないという…。
Re: (スコア:0)
Cの前にマシン語をやっていたので、ポインタは理解できた(というか、ポインタが理解できない人のことが理解できない)
のですが、関数へのポインタの配列を返す関数にtypedefして~とか言うと、ついていけなくなります。
それから、
は理解できますが、
と書く発想が理解できません。「int*」って何だよ?って。
Re:プログラミング言語C (スコア:2, すばらしい洞察)
Cの前に、マシン語をやっていたので
多分同じようなもんだと思いますが、
int* i;
は私にはしっくりきましたよ。
Javaやってる人は
int[] i; // 配列型のiという変数
という書き方をするので、同意してもらえるかも。
変数名とその振る舞いは分離しておきたい、というだけです。
/* なんにせよ、ポインタ変数なのか、普通の変数なのかがよく分かるように
* 命名していれば混乱の問題は無いと思うんですが、
* a,b,i,j,kで使われる例ばっかりなのがよくないのかなぁ、と・・・
*/
Re: (スコア:0)
くらいなら、そういうものだと天下り式に覚えてしまってもいいのですが、
それだと、応用が利きません。たとえば、関数ポインタ [sakura.ne.jp]というページには、
という例が載っていますが、この(*po[])()という部分について、演算子の優先順位の低い部分から順番に剥ぎ取っていって、
Re:プログラミング言語C (スコア:1)
>void (*po[])()
を
int* (*po[])()
としてみると、
・(*po[])() は、int*である
以下、void→int*で置換して終了。
うむ、応用が効きそうだ、とか言ってみる。
この場合逆に
int *(*po[])()
と書いたら最初の「*」がどちらに付くか迷いそうな気もしないでもない。
けど
int (*p)[5];
のような場合は int* だと書けないっぽいね。
#void func(int **p); int a[5][10]; func(a);
#の間違いが分からなければもう一度プログラミング言語Cをやり直し!
Re: (スコア:0)
> int* (*po[])()
> としてみると、
> ・(*po[])() は、int*である
「po」の直前の「*」を、「int*」のように型宣言の側に出す方法はありますか?
そうでないと、変数名の側に「*」を書く文法の必要性が残ってしまいますので、
それなら単純なポインタも「int *i;」でいいじゃん、ということになってしまいます。
同じような例だけど、
int *p[];
は
int* p[];
と書き直せるけど、
int (*p)[];
は、どのように書き直したらいい?
> この場合逆に
> int *(*po[])()
> と書いたら最初の「*」がどちらに付くか迷いそうな気もしないでもない。
それは、演算子の優先順位を理解してないからでしょう。
Cの原則として、スペースの位置で意味が変わってはいけないのでは。
Re: (スコア:0)
> 「po」の直前の「*」を、「int*」のように型宣言の側に出す方法はありますか?
typedef int* (po_func_t)(void);
po_func_t* hoge[] = {kitty , sakura , hina};
外にでました!
> int (*p)[];
> は、どのように書き直したらいい?
typedef int int_array_t[];
int_array_t* p;
で行けます!
…とか、そういう話ではないのは理解はしているが、
基本的にどちらの構文も同じように正常にコンパイルできるからには、
「どっちに * をつけたっていい。」ってのが正しい事だと思う。
私は typ
Re: (スコア:0)
普通のプログラマはそんなトリッキーなことを考えずに、コンパイラの最適化に任せてだらだらと冗長に書けばいいんだと思います。
Re: (スコア:0)
C#とDはint* i,j,kがCのint *i, *j, *kになるように改良されていますね。
配列も同様にint[] arrayと[]を型側に書くようになっている。
Cという文脈で話す限りint *iという解釈をするほうが落とし穴に落ちにくいのは
確かですが、やはりそれはCの構文上の失敗を解釈によってフォローしているだけで、
型に含められるほうが自然だと思います。
Re:プログラミング言語C (スコア:1, 興味深い)
pint_t a, b, c;
…わかりやすいでないですか。
と、いうのは全然嘘です。
私もその書き方が気に入ってません。
int* i;
と書かなければ気が済まない人というのは、
「ポインターなのは型だろー、型は型として書かせてくれよ。」
って思ってる人たちなんです。
きっと型名にスペースを含んでいるのが許せない人達なんです。
または、* が型名に含まれていない事が我慢できない人達なんです。
そう思ってる人たちは決して、
int* i, *j, *k;
とは書かない/書けないみたいですね。
そんなに嫌なら、typedef しやがれですよ。> 各位
Re: (スコア:0)
初心者の頃は、「intのpointer型」だと思ってたから、int* i; て書いていたよ。
でも、ご多分に漏れず int* i, j; て書いてバグってから、
「int型へのpointer」なんだって理解になって int *i, *j; と書くようになった。
いまでは、int* i; は見ていてなんか気持ち悪い。
今、手元のK&Rを読むと、*はちゃんと型名の方ではなく変数の方についていますね。
石田先生、C言語を日本に広めてくれた立役者として、感謝致します。
ご冥福をお祈り致します。
Re: (スコア:0)
> でも、ご多分に漏れず int* i, j; て書いてバグってから、
型をどう見なすか?と、
Parse時の結合則がどうなってるか?(つまり文法)とは
別の問題です。
そう見ることが出来ていれば、上記のような間違いも起こさないし、そういうソースに出会っても理由がすぐに判ります。
…というキッチリした視点を私に叩きこんでくれたのがあのC本でした。
あの本が無ければ私は「自在にプログラミングする」能力など到底持てず、
Cを(他の言語でも)ちょいと書いてはバグって理由が判らずウダウダするばかりの
三流似非プログラマにしかなれなかったことでしょう。
(それ以前にプログラマ/趣味グラマになってなかったかも知れないが)
早い話が我が人生最大の恩人というか恩本がアレです。
ひたすら冥福をお祈りいたします。
Re: (スコア:0)
そうなんですけど、それがわかりにくいんですよ
そのわかりにくさは御大も認識しているわけで
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:プログラミング言語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:プログラミング言語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:プログラミング言語C (スコア:1)
> 関数へのポインタの配列を返す関数
などの型が理解できない、という意味だと思っていたのですが、最近になって違うことが分かってきました。
それは置くとして、私は読む分には
int*i;
int *i;
int* i;
int * i;
どれでも一緒です。たぶん最後は私は書かないけど上の3つは気分によってどれも書くかも。
寧ろポインタ変数の名称がiであるのが少しだけ気持ち悪いかな(コメント主はそこは問題にしていないと思うけど)。
とはいいつつ、それもそこまで気にしないです。
int* i, *j, k[5];
とかでも平気です。
Best regards, でぃーすけ
Re: (スコア:0)
今は後者が主流な気がする
本の影響もあってC++使いは特に
Re:プログラミング言語C (スコア:2)
私の場合、Z80世代からパソコンをはじめて、今の本職はC++ですが、
int *p;
の書き方じゃないと落ち着きません。その変数がポインタであることが見た瞬間に理解できるような書き方をしたいので、*と変数名はつなげて書きたいです。
初期のCを飛ばして、C++やJavaから入った人だと、
int* p;
と書くようです。
そういう人は、 unsigned char* p; とか書くんでしょうか?
unsignedとcharの間にスペースが入っているのに、charと*の間が詰まってるのって、気持ち悪くないですかね?
こういうのを見る度に、こっそり書き直したくてそわそわします。しかし、バージョン管理システムに履歴が残ってしまうので、迂闊に書き直すわけにもいきません。まったく困ったものです。
Re:プログラミング言語C (スコア:1)
以下の C++ における char* p; なコードは、char *p; 派としてはどのように書くと一番しっくり来ますか?
C++ は char *p; より char* p; だと思いますよ。
Re:プログラミング言語C (スコア:2)
私は個人的にconstは後ろ寄りに書く習慣でやっていますので、
const char *p; ではなく、 char const *p; と書きます。
従って、 const char* const str = "ABC"; は、 char const *const str = "ABC"; になります。
まあ「*const」の部分はちょっと微妙なので、間にスペースを入れても入れなくてもいいかなと思います。
そんなわけで、やっぱり C++ でも char* p; より char *p; だと思いますよ。
Re:プログラミング言語C (スコア:1)
"const str" に * を付ける、という感じですか。なるほど。
しかし個人的には *const はかなり座りが悪い感じがするので、それならいっそ char * p; でいいようにも思えますね。
Re: (スコア:0)
では、
unsigned char * p;
これはどうですか。
ちなみに、
int *a,*b,*c;
のつもりで
int* a,b,c;
なんてやったら・・・ということを心配するならば、まず、
int* a;
int* b;
int* c;
と書くことから先に指摘すべきです。
Re:プログラミング言語C (スコア:1)
「pはポインタである」が真っ先に関連づけられることから、*pと書くのが理にかなっていると思うのですが。
話はそれますが、WindowsのAPIでよく現れるような、LPDWORD p;の様な、わざとポインタを隠蔽するような書き方は、愚の骨頂だと思うです。
Re:プログラミング言語C (スコア:1, 興味深い)
の人は、pはint型を指す「ポインタ」だから、という感じだと思いますが、
C++でテンプレートだとか無名引数とか使うと、pはint型を指す「ポインタ型」と、
型全体に着目する事が増えるので int* pの方が自然に感じてくるだけです。
私にとっては
typedef Class1< int *, int * > IClass1;
より
typedef Class1< int*, int* > IClass1;
の方が、テンプレート引数の型が「int型を指すポインタ型」だとわかりやすいと
感じるので、変数の宣言にも同様のルールを当てはめるわけです。
ポインタ型の無名引数の場合も同様です。
あと、const int* const p = &a;
みたいな、constオブジェクトを指すconstなポインタは、*pな場合書き辛いってのもあります。
Re:プログラミング言語C (スコア:2)
いっそのこと、複数の変数をまとめて宣言できるってのを廃止するってのは?
int a;
char b;
int* c;
char* d;
としか書けなくすれば万事解決です。
Re: (スコア:0)
ハンガリアン記法でも使っていて下さい。
> 初期のCを飛ばして、C++やJavaから入った人だと、
> int* p;
> と書くようです。
初期のCを使って、その後の変化についていけない人だと
int *p;
と書くようです。
"型宣言 変数名"という形で統一した方がわかりやすいと思うのですが。
なぜポインタ型だけ特別視するのでしょう?
Re:プログラミング言語C (スコア:1)
別にポインタを特別視はしていません。
「pはポインタである」→ *p
「*pはintである」→ int *p
と読み下せます。(と多くの方が同様な主張をされていると思います)
同じ理屈で、
「funcはポインタである」→ *func
「*funcは関数である」→ (*func)()
「(*func)()はintを引数にとる」→ (*func)(int)
「(*func)(int)は結果を返さない」→ void (*func)(int)
故に、funcはintを引数にとり、結果を返さない、関数へのポインタである。
のように自然に読み下せます。
だから、*と変数名はつながっていないと、様々な型宣言で破綻します。
Re:プログラミング言語C (スコア:1)
C言語の構文解析上は「int *p」も「int* p」も同じですから、
どっちで書くかは、文法の話ではなく「コーディングスタイル」の問題じゃないですかね。
まあ、スタイルの問題だからこそ、どちらも派閥も譲れないんだと思いますが
私は「int *p」と書く方ですが、そう書くようになったきっかけは「K&R で int *p と書かれていたから」です。
ちゃんと文法を知ってからは、「ポインタの * は右結合なので、*pとくっつける」と、スタイルの意味は理解しましたが、それは後付けです。
そういう構文解析が行われるからといって、「int* p」と書くことが文法違反になるわけじゃありませんし。
その後、 Stroustrup を読んで「int* p」と書く流儀の存在を知りましたが、あまりなじめず、
C++でも「int *p」と書くのを貫いてます。
#「ifやwhileなどの予約語の後(の間にはスペースを入れる」「関数とその後の(の間にはスペースは入れない」
#「ifやwhileと同じ行の終わりに { を入れる」など、基本的なコーディングスタイルはK&Rで通してるんですが、
#インデントが5タブなのだけは、さすがに真似できなかった…
Re: (スコア:0)
>なぜポインタ型だけ特別視するのでしょう?
アセンブラ寄りの人にとってはポインタ型という概念自体が気持ち悪いからじゃね?
Re: (スコア:0)
>アセンブラ寄りの人にとってはポインタ型という概念自体が気持ち悪いからじゃね?
それは86系に限っての話じゃないですかね。
Re: (スコア:0)
char *p, *p2ならそう書きます
char *pの場合は目が疲れてぼやけた状態で見るとcharと見間違えるときがあります
またchar* pのほうがなんとなく打ちやすいので変数1つの時はchar* pって書いてます
Re: (スコア:0)
Z80アセンブリは判りますが、C言語はポインタで挫折
ポインタをZ80のニモニック的に教えてください...........orz
Re: (スコア:0)
レジスタに入っている数値をアドレスとみなして、メモリにアクセスすることです。
アセンブラやってる人なら、ごくふつうにやってることです。
というのが、
というのに相当します。アセンブラには型がないので、数値をアドレスとみなすのは
人間の勝手ですが、Cだとアドレス専用に使う変数があって、それがポインタです。
Re:プログラミング言語C (スコア:1)
Re: (スコア:0)
Re:プログラミング言語C (スコア:1)
「深い」って「判りにくい」の別名だと思うので、あんまりプログラミング言語に積極的に求められる性質ではなく、肯定的に捉えられる性質でもないんじゃないかなと思います。
特に、その時代の技術では代替の効きにくい重要必須なコンセプトやメカニズムならともかく、単なる構文的な判りにくさにはあまり入れ込むのはどうかなーと。
ポインタ型の観念や機構は重要だろうけど構文はそれに比べてそれほど重要でないんじゃないかと思います。
それでもCぐらいだと構文の瑕疵まで含めて完全に理解しようと思えばできなくはないのでしょうが、C++あたり使ってると「ギリギリまで使い切れる完全な理解に基づいて書く」ではなくて「より限定的だけど読みやすく、瑕疵には触れず安全目な理解の範囲で書く」へと段々割り切りがw
(で、ギリギリのことをしなければならない羽目に陥ったらそのときはあきらめてリファレンスを読み直す。)
まぁ、「C実践プログラミング」本とか「MISRA C」規約的な方向性ですね。(とはいえ私個人はそこまでストイックではないですが。)
Re: (スコア:0)
「pはcharのポインタ」と教えようとするからわかりにくくなるらしいですね。
「C言語では宣言時には使うときと同じように書く」のが基本なので、
char *p;
と書いて「*pはchar」と教えればいいらしいです。
char *p, q;
でも「*pとqはchar」と、ごく自然に教えられます。
Re: (スコア:0)
>関数へのポインタの配列を返す関数にtypedefして~とか言うと、ついていけなくなります。
ジャンプテーブルですよね?
普通にマシン語でよく使う手だと思うんですけど・・・
Re: (スコア:0)
買った本屋でつけたもらったカバーが擦り切れ、本についていたカバーも擦り切れるくらい読みました。
C言語での自分のスタイルが確立した後も1年に1回読み直したりしていました。
時代の流れを感じます。
ご冥福をお祈り申し上げます。
Re: (スコア:0)
Re: (スコア:0)
そこで挫折して同時にやってたLispのリスト構造の美を悟ったのでした。
carとcdrはポインタが理解できないやつのためにあるのだと。
Lispに追いやってくださった石田先生、こころよりご冥福をお祈り致します。
# 誰かこれ [technobahn.com]タレこんでくんね?
Re:プログラミング言語C (スコア:1)
carとcdrが理解できずにポインタの道に進んだ人も多いのではないでしょうか?
Re: (スコア:0)
再帰とか、高階関数とか、変数と手続きの区別を付けにくく、プログラムの流れを目で追いにくいというのが、LISPの難しいところではないかと。
プログラミング言語Cの誤訳問題 (スコア:0)
「プログラミング言語Cには誤訳が多いせいで余計に難解になっている」って話、ありませんでしたっけ?
あれ読んで理解出来る人は偉いと思います。
そのせいで読めなかったとは言いませんが、結局「現実的なCプログラミング」を読むまで、殆ど理解出来
ませんでした。