ページ 1 / 1
配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 20:18
by YUKI007BKB
配列へのポインタ 「 (*a)[10] など 」 について質問です.
いろいろなサイトを調べてみましたが,わかりやすい説明や具体例が載ってなく,なんとなく自分で以下のようなプログラムを組んでみました.
コード:
#include <stdio.h>
void foo(int (*)[10]);
int main(void){
int a[10];
int i;
for(i=0;i<10;i++) a[i]=i;
foo(&a);
for(i=0;i<10;i++) printf("a[%d]=%2d\n",i,a[i]);
return 0;
}
void foo(int (*x)[10]){
(*x)[4]=40;
}
[実行結果]
a[0]= 0
a[1]= 1
a[2]= 2
a[3]= 3
a[4]=40
a[5]= 5
a[6]= 6
a[7]= 7
a[8]= 8
a[9]= 9
プログラム自体は正しく動いたのですが,結局,配列へのポインタを使うメリットがわかりませんでした.
実際,下記のように関数の引数として渡すものを配列にして実行してみても上手くいきました.
コード:
#include <stdio.h>
void foo(int*);
int main(void){
int a[10];
int i;
for(i=0;i<10;i++) a[i]=i;
foo(a);
for(i=0;i<10;i++) printf("a[%d]=%2d\n",i,a[i]);
return 0;
}
void foo(int x[10]){
x[4]=40;
}
どのようなときに配列へのポインタは効力を発揮するのか,できればそのサンプルプログラムなども教えていただけると幸いです.
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 22:13
by Math
2番目のプログラムは正しいです。
C言語の正書法では
コード:
#include <stdio.h>
void foo(int*);
int main(void){
int a[10];
int i;
for(i=0;i<10;i++) a[i]=i;
foo(a);
for(i=0;i<10;i++) printf("a[%d]=%2d\n",i,a[i]);
return 0;
}
void foo(int* x){
*(x + 3)= 40; // x[4]=40; でも良いが C言語のシンタックス・シュガーです
}
1番目のプログラムは大幅に間違えています。
プロトタイプ宣言には仮変数名が必要です。
実引数 int (*x)[10]) は 2次元配列 x[][10] に相当します。
したがって a[5][10] のようにします。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 22:21
by Math
引数配列は正書法であるポインターに読み替えられます
int x[5][10];
int x[][10];
は
int (*x)[10]; シンタックスシュガーです。
https://ja.wikipedia.org/wiki/%E7%B3%96 ... B%E6%96%87
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 22:23
by Math
「訂正」
int (*x)[10]; の シンタックスシュガーです。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 22:23
by Math
「訂正」
int (*x)[10]; の シンタックスシュガーです。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 22:51
by Math
sample by gcc8.2.0
コード:
#include <stdio.h>
void foo(int (*x)[10]);
int main(void){
int a[5][10];
foo(a);
for(int i=0;i<5;i++){
for(int j=0;j<10;j++){
printf("a[%d][%d]=%2d:",i,j,a[i][j]);
}
printf("\n");
}
return 0;
}
void foo(int (*x)[10]){
for(int i=0;i<5;i++){
for(int j=0;j<10;j++){
x[i][j] = i*10 + j;
}
}
}
実行
コード:
C:\19\19c\gcc820>gcc main.c
C:\19\19c\gcc820>dir a.exe
ドライブ C のボリューム ラベルがありません。
ボリューム シリアル番号は 7813-6100 です
C:\19\19c\gcc820 のディレクトリ
2019/06/04 22:47 42,774 a.exe
1 個のファイル 42,774 バイト
0 個のディレクトリ 118,261,030,912 バイトの空き領域
C:\19\19c\gcc820>a.exe
a[0][0]= 0:a[0][1]= 1:a[0][2]= 2:a[0][3]= 3:a[0][4]= 4:a[0][5]= 5:a[0][6]= 6:a[0][7]= 7:a[0][8]= 8:a[0][9]= 9:
a[1][0]=10:a[1][1]=11:a[1][2]=12:a[1][3]=13:a[1][4]=14:a[1][5]=15:a[1][6]=16:a[1][7]=17:a[1][8]=18:a[1][9]=19:
a[2][0]=20:a[2][1]=21:a[2][2]=22:a[2][3]=23:a[2][4]=24:a[2][5]=25:a[2][6]=26:a[2][7]=27:a[2][8]=28:a[2][9]=29:
a[3][0]=30:a[3][1]=31:a[3][2]=32:a[3][3]=33:a[3][4]=34:a[3][5]=35:a[3][6]=36:a[3][7]=37:a[3][8]=38:a[3][9]=39:
a[4][0]=40:a[4][1]=41:a[4][2]=42:a[4][3]=43:a[4][4]=44:a[4][5]=45:a[4][6]=46:a[4][7]=47:a[4][8]=48:a[4][9]=49:
C:\19\19c\gcc820>pause
続行するには何かキーを押してください . . .
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月04日(火) 23:41
by Math
C言語はあまり気のきいた言語ではありません。
配列を渡しても先頭アドレスが渡るだけで要素数さえわたりません。
しかし 人間に分かり易いシンタックスシュガーを使ってもコンパイラが最適化してくれるので
昔ほどポインターを駆使する必要はありません。
しかし文法がむつかしいのでこうゆう場合悩ませれますよね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 00:03
by Math
C言語には本当の意味の2次元配列は存在しません。
2次元配列にみえるのは配列の配列です。
従って2次元配列を1次元配列として読んだり
1次元配列を多次元配列のようにできますよ。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 06:35
by Math
この本が唯一の正しいC言語の本だとおもいます。
疑問を持たれたら読んでみるといいですよ!
http://kmaebashi.com/seiha2/index.html
ーーーーーーーーーーーーーーーーーーーーーーーーーー
この本は、2001年の発売以来たいへんご好評をいただいた『C言語 ポインタ完全制覇』の改訂版です。
旧版は、最終的に第18刷を数えるロングセラーとなりました。読んでくださる皆様のおかげです。
今回の版では、時代に合わせたり、旧版では不足していたと思われる記述を補ったりという加筆・修正を行いました。
------------------------------------------------------------------------
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 06:56
by Math
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 08:09
by Math
式中において [] は 配列の意味ですらない。
したがってつぎのプログラムは正しく 正常に実行される(^^;
gcc8.2.0使用
コード:
#include <stdio.h>
void foo(int*);
int main(void){
int a[10];
int i;
for(i=0;i<10;i++) i[a]=i; // a[i]=i;
foo(a);
for(i=0;i<10;i++) printf("a[%d]=%2d\n",i,i[a]); // a[i]);
return 0;
}
void foo(int x[10]){
4[x]=40; // x[4]=40;
}
じっこう
コード:
C:\19\19c\gcc820>gcc main.c
C:\19\19c\gcc820>dir a.exe
ドライブ C のボリューム ラベルがありません。
ボリューム シリアル番号は 7813-6100 です
C:\19\19c\gcc820 のディレクトリ
2019/06/05 07:58 42,687 a.exe
1 個のファイル 42,687 バイト
0 個のディレクトリ 119,338,401,792 バイトの空き領域
C:\19\19c\gcc820>a.exe
a[0]= 0
a[1]= 1
a[2]= 2
a[3]= 3
a[4]=40
a[5]= 5
a[6]= 6
a[7]= 7
a[8]= 8
a[9]= 9
C:\19\19c\gcc820>pause
続行するには何かキーを押してください . . .
a
は *(a+i) のシンタックス・シュガーであり *(i+a) つまり i[a] と同じですね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 08:18
by Math
詳細は過去ログに何度もかいたので検索してください。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 08:28
by Math
a[ i ] は *(a+i) のシンタックス・シュガーであり *(i+a) つまり i[a] と同じですね。
( a[ i ] はスペースを省くと イタリック体 指示とみなされ きえちゃうんですね(^^; )
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 11:38
by かずま
YUKI007BKB さんが書きました: ↑5年前
コード:
void foo(int (*x)[10]){
(*x)[4]=40;
}
この関数の仕様は、
「要素数 10個の配列の先頭から 4つ先の要素を 40に変更する」
ですね。
呼び出し側が間違って、要素数が 10でない配列のアドレスを渡そうと
するとコンパイルエラーになり、間違いを検出できます。メリットです。
要素数が 10でない配列には使えません。デメリットです。
YUKI007BKB さんが書きました: ↑5年前
コード:
void foo(int x[10]){
x[4]=40;
}
この関数の仕様は、
「要素数 5個以上の配列の先頭から 4つ先の要素を 40に変更する」
ですね。
要素数が 5以上の配列なら何でも渡せます。メリットです。
「配列へのポインタ」は二次元配列の受け渡しに利用できます。メリットです。
コード:
#include <stdio.h>
void func(int (*x)[4]) // int x[3][4] の配列の表示
{
for (int i = 0; i < 3; i++, putchar('\n'))
for (int j = 0; j < 4; j++) printf(" %4d", x[i][j]);
}
int main(void)
{
int a[3][4] = {
{ 11, 12, 13, 14 },
{ 21, 22, 23, 24 },
{ 31, 32, 33, 34 },
};
func(a);
}
通常の宣言では、
コード:
int a[3][4]; // a は配列
int (*p)[4]; // p はポインタ
と完全に別物ですが、
「関数の仮引数」の宣言に限り、次の 3つは同じです。
コード:
void func(int (*p)[4]) // p はポインタ
void func(int p[][4]) // p はポインタ
void func(int p[3][4]) // p はポインタ
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 12:16
by YuO
YUKI007BKB さんが書きました: ↑5年前
どのようなときに配列へのポインタは効力を発揮するのか,できればそのサンプルプログラムなども教えていただけると幸いです.
明示的に配列へのポインタを記述する必要は、ほぼないと思います。
多次元配列を引数にとるときに、そのまま書いたもの
コード:
void function (int array[5][10]);
はコンパイラによって配列へのポインタとして書いたもの
コード:
void function (int (*array)[10]);
とみなされますが、前者の方が読みやすいですし、配列へのポインタで書く必要はないでしょう。
オフトピック
C99以降、配列でのint array [static 5][10]をポインタで書く方法がないため、配列のまま書いた方が情報量が多くなります。
現実的にはstaticとかrestrictとかの「CにはあるがC++にはないもの」は使いにくいとは思いますが。
なお、標準外ではありますが多次元配列という言葉はISO/IEC 9899:2011中footnote 142を定義に利用しています。
あとは参照する多次元配列を条件によって決める場合には使えますが、
コード:
int array1[10][10] = { ... };
int array2[10][10] = { ... };
_Bool condition = 1; // or 0
int (*ptr)[10] = condition ? array1 : array2;
そもそも多次元配列自体を条件で決めたり取り替えたりする機会は滅多にないので、こちらの使い方も少ないと思います。
単純に知識としての持っておくと、武器になる「場合がある」程度に思うとよいかと。
► スポイラーを表示
オフトピック
► スポイラーを表示
Math さんが書きました: ↑5年前
1番目のプログラムは大幅に間違えています。
ん?
Math さんが書きました: ↑5年前
プロトタイプ宣言には仮変数名が必要です。
要出典。
なお、ISO/IEC 9899:2011においては、6.7.6.3 Function declarators (including prototypes)において、
ISO/IEC 9899:2011 さんが書きました:6 A parameter type list specifies the types of, and may declare identifiers for, the parameters of the function.
と「may」であって、「should」ではないです。
ISO/IEC 9899-1990の現物がないのでJIS X 3010-1993を参照すると、6.5.4.3 関数宣言子(関数原型を含む)において、
JIS X3010-1993 さんが書きました:仮引数型並びは,関数の仮引数の型を指定する。仮引数の識別子を宣言してもよい。
と「してもよい」であって、「しなければならない」ではないです。
関数の定義では、6.9.1 Function definitionsに
ISO/IEC 9899:2011 さんが書きました:If the declarator includes a parameter type list, the declaration of each parameter shall include an identifier, except for the special case of a parameter list consisting of a single parameter of type void, in which case there shall not be an identifier.
とあるために識別子が必須ですが、宣言においてこれは関係しません (正確に言えば、関数の定義である宣言以外には関係しない)。
Math さんが書きました: ↑5年前
実引数 int (*x)[10]) は 2次元配列 x[][10] に相当します。
したがって a[5][10] のようにします。
これは、
YUKI007BKB さんが書きました: ↑5年前
コード:
void foo(int (*x)[10]){
(*x)[4]=40;
}
ここへの指摘っぽいけれども、何も間違っていません。
このコードはx[0][4]と同じ意味です。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 19:22
by Math
わけのわからんことをごたごたいうと まさに初心者のかたを混乱させるだけなので
式の中 とくに関数の引数のときに ポインターで受けるのがC言語流のやりかたで
void foo(int x[10]) は
void foo(int *x) となる
void foo(int x[][10]) は
void foo(int (*x)[10]) となる
これで十分だよね!
( 最初は納得しにくいですよね。紹介した本をかいたひとも3年かかってわかったそうですよ(^^; )
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月05日(水) 19:31
by Math
いまは 普通に配列流に書けばいいですよ。
私は大型機で何万行というC言語のプログラムを書いたけどポインタを意識したことはありませんよ。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月08日(土) 19:35
by テリー・サバラン
>配列へのポインタを使うメリットがわかりませんでした.
メリットは、処理時間とシステム資源(メモリ)の節約。
以前、アプリケーションソフトを作った時に、構造体配列の並べ替えをしたことがありました。
その構造体の1要素はデータ量が大きいため、実際に構造体データを並べ替えるのではなく、構造体の配列要素へのポインタ配列を並べ替えることで、処理時間を短縮できました。
また、例えば10パターンで並べ替えた結果が必要な場合に、もとの構造体配列を9回コピーして10個にし、そのデータを並べ替えるよりも、構造体配列へのポインタ配列を10個作成し、そのポインタ配列を並べ替える方が効率的です。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月12日(水) 07:08
by Math
#18 に見当違いなことがかかれているのでちょっとせつめいすると
「Cの暗部のひとつ配列とポインタ」について
Cをそれなりに習得していても以下のような疑問を感じる人が多い。
•Cの宣言では、[] は* よりも優先順位が高い。 ゆえに、char *s[10]; という宣言は、「char へのポインタの配列」を意味する ---逆じゃないか?
•double (*p)[3]; や、void (*func)(int a);といった宣言の読み方がわからない。
•int *a;は、a を「intへのポインタ」として宣言する。 しかし、式の中での * は、ポインタをポインタでなくする方に働く。同じ記号なのに、意味が逆じゃないか?
•int *a と int a[] は、どのような場合に置換可能なのか?
•空の [] は、どのような場所で使用することができ、 どのような意味を持つのか?
このような点については、Cの参考書において、 記述されていないか 著者が明らかに勘違いをしているかのどれかである。
Cのバイブルと呼ばれる「プログラミング言語C」(K&R) 第2版の日本語訳改訂版に従う)も例外ではない (というより、この本(の原書)が諸悪の根源かもしれない)。
Cの文法は、一見首尾一貫しているようで、実は数多くの例外が存在する。
[上記の本、サイト参照]
以下時間の取れる時にこの場合の説明を記す。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月12日(水) 14:50
by YUKI007BKB
Math さんが書きました: ↑5年前
わけのわからんことをごたごたいうと まさに初心者のかたを混乱させるだけなので
式の中 とくに関数の引数のときに ポインターで受けるのがC言語流のやりかたで
void foo(int x[10]) は
void foo(int *x) となる
void foo(int x[][10]) は
void foo(int (*x)[10]) となる
これで十分だよね!
( 最初は納得しにくいですよね。紹介した本をかいたひとも3年かかってわかったそうですよ(^^; )
Mathさん,返信遅くなってしまい申し訳ございません.
丁寧に例も交えながらたくさん教えてくださり,本当にありがとうございました!
正直,完璧に理解したと言える自信はないですが,回答をじっくり読んだり調べたりする中で多くのことを学べました.
・シンタックスシュガー
・a[i]=*(a+i)=*(i+a)=i[a]
などは特にC言語の教養として大変興味深いものでした.
また自分が質問していて,お時間がありましたらお力を貸していただけると幸いです(;_;)
改めまして,本当にありがとうございました!
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月13日(木) 07:33
by ISLe
オフトピック
Cの文法は、意味に一貫性を持たせているのではなくて、表記に一貫性を持たせている。
K&Rにも、記述や表現の一貫性というふうに、書いてある。
表記に一貫性を持たせれば、宣言するときと式の中で使うときとで意味が逆になるのは当然のことじゃあないのか。
例えばポインタを
int >a;
というふうに宣言して
<a で間接参照するようにしたら分かりやすいか?
そうは思わない。
うっかり>aと書いた場合どうしたらいい?
むしろうっかりミスの機会が増える。
宣言では>しか使わないし、間接参照では<しか使わない。
だったら同じ記号でいいじゃないか。
逆に書いてしまうミスも防げる。
オフトピック
アセンブラだとアドレスが1バイトずつ進むのが当たり前のように書いてるヒトって8ビットCPUのころに触っただけなのかなって思ってしまうけど、実際どうなんだろう。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月13日(木) 15:51
by かずま
#2 に見当違いなことが書かれているのでちょっと指摘します。
Math さんが書きました: ↑5年前
1番目のプログラムは大幅に間違えています。
どこにも間違いはありません。
Math さんが書きました: ↑5年前
プロトタイプ宣言には仮変数名が必要です。
「仮変数」というのは「仮引数」の間違いではありませんか?
プロトタイプ宣言には仮引数名はあってもいいし、なくても構いません。
void foo(int (*x)[10]);
void foo(int (*)[10]);
どちらでもよい。
Math さんが書きました: ↑5年前
実引数 int (*x)[10]) は 2次元配列 x[][10] に相当します。
int (*x)[10] は実引数ではありません。仮引数です。
実引数は、関数呼出し元の引数です。型名の int があってはいけません。
例えば #1 の1番目のプログラムの f(&a); の &a です。
仮引数 int (*x)[10] に対して、実引数が常に 2次元配列である必要はありません。
int a[10]; という 1次元配列があれば、&a という実引数は正しい。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月13日(木) 18:09
by usao
既に述べられていることの繰り返しになるが,
コード:
void foo(int x[10]){ ... }
は
コード:
void foo( int *x ){ ... }
と同じであって,"[10]" は何もしてくれない.
コードを見た人間が「要素数が10個の配列を渡して欲しいんだろうなぁ」と察することの役には立つかもしれないが,
「うーん,でも今日は要素数が7個の配列を渡してみるか!」というのをコンパイルエラーにできない.
対して,件の"配列へのポインタ"では,コンパイルエラーにできる.
"配列へのポインタ"を使う例を考えるならば,
例えば「俺は3次元の座標(ベクトル)を 要素数3の配列 で扱うぜ!」ということにしたならば,
きっと3次元のベクトルに対する何らかの操作を行う関数群を書きたくなるハズだ.そのときに
コード:
void DoSomethingFor3DVector( double *V )
{
変なものが渡されてきたら動作は不定になるのに,
この関数の実装ではそのチェックすらできない…
}
という形の関数群を山ほど書くのか?っていう.
別にそれでも良いかもしれないが,
多少の利便性を犠牲にして"配列へのポインタ"を引数にして
コード:
void DoSomethingFor3DVector( double (*V)[3] )
{
nullでないなら,要素数3個であることは信じてもいいよねきっと
}
とすることで,より心安らかになれるかもしれない.
この例のような場合なら,関数の外側でもほとんどの箇所では座標データを要素数3個の配列で扱っていることを想定しても良さそうだから
言うほど利便性は犠牲にならず,不用意なバグを排除できるというメリットの方が大きいかもしれない.
不便さの程度で言えば,3次元座標(ベクトル)のための型として
コード:
struct Vec3D{ double Value[3]; };
みたいな専用のものを使うことを強要することとほぼ変わらない程度だろう.
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月18日(火) 21:00
by Math
#16, #17 で質問様は検証され納得されていて正しいのだが 一応(*a)[10] について蛇足ではあるがせつめいする。
int (*a)[10] は英語では a is pointer to array(要素数10) of int
は日本語では a は、int の配列(要素数10)へのポインタである
"K&Rによれば、Cの宣言は、「変数が現われ得る式の構文を真似た(P.114)」 そうである。しかし、本質的に全く異なるものを無理に似せようとしたため、 結局わけのわからない構文になってしまっている。 「宣言の形と使用時の形を似せる」というのはC(およびCから派生したC++, Javaなどの言語)に特有の「変な構文」である。"
と前橋さんはかいておられます。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月18日(火) 21:32
by Math
C言語はUnixを開発するために作られ 急いでいたので内輪だけわかってればよいという環境だったらしいが
Unix がメジャーになりCもデファクトスタンダードになったということですね。
そういえば私がCで開発した大型機(CPU256個実装)もUnixでしたね!
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月19日(水) 08:11
by ISLe
オフトピック
本質的に違うもの、という意味では、値型と参照型を同列に扱うモダンな言語のほうが、よほど奇妙に感じますけどね。
値型と参照型を同列に扱うほうが、どうして受け入れられやすいのかは、ポインタが難解だと言われるのと同じ理由です。
(ただの)変数を深く理解しないままでも、コードを書ける、からコーディングが楽。
(ただの)変数を深く理解しないまま、コードを書いている、からポインタで壁にぶち当たる。
初心者は、
「変数の値は、コンピュータが適当に覚えていてくれて」
「変数が使われるときに、式が書き換えられる」
というふうに理解しがち。
バリエーションはありますが、共通ポイントはこの2点。
こういうふうに変数を理解していると、その人の頭の中に、ポインタが差すべきものが存在しない。
そこからポインタを理解しようというのは、ゼロからのスタートどころか、それまで正しいと思っていた変数に対する考え方から改めなければいけない上に、刷り込まれてしまった考え方がふとしたときに蘇ることもあって、マイナスです。
値型と参照型を同列に扱う言語は、そういうふうに変数を理解していても、支障ないんです。
変数に関して言えば、今風のプログラムは、プログラマの思った通りに動いている、わけですね。
k&Rが元凶だとか諸悪の根源だとか言われますが、文章として書かれていること以外の重要な要素がきちんと反映されている解説を見たことがないので、言いがかりとしか思えないんですよね。
K&Rのポインタの章の頭は、
1. 変数が変数を差すメモリのイメージ図
2. 1.のイメージを実現するための代入の形
3. ポインタ変数の宣言
という流れになってます。
そして、初心者向け(?)のポインタの解説はたいていその逆です。
#少なくとも逆じゃないのを見たことがない。
K&Rでは、ポインタ変数も当然メモリに割り当てられている図が描かれているけども、ポインタがどこからともなく変数名だけでメモリを差している図が描かれている解説も少なくない。
K&Rは、ポインタ以前に、ただの変数がオブジェクトであることをきちんと理解していないと理解できない流れになっていて、これってとても重要なことだと思うのです。
これを難解と呼ぶなら呼べば良いですが、ただの変数をきちんと理解できてないのにポインタを扱うのは、危険だとか言う前に、そもそもとっかかりがないから試行錯誤にしかならんのです。
理解が曖昧なままでも使う上では問題にならない、ただの変数を扱う段階で、きちんと理解を深めておく、というのが重要じゃあないでしょうかね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月19日(水) 10:50
by usao
オフトピック
> 値型と参照型を同列に扱うほうが、どうして受け入れられやすいのか
コレ,世間では受け入れられやすいのか…
少なくとも今現在半径3mの範囲にいる人々(と私)の場合は
「ん? なにそれ? ちょっともう一回言って?」みたいな反応だったんで,かなり異端な集団だったのだなぁ…
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月22日(土) 08:29
by Math
前橋さんは C のポインタ は全然むつかしくない (私も小学生でも十分わかると思う)
ただC のぶんぽう が混乱して例外がおおいから ベテランといわれるひとが”むつかしいぞ”
と脅すのが原因だとおっしゃっておられます。(ベテランも知らないか 知っていても忘れたりするらしいですよ・)
私も普通につかってればポインタを意識する必要がなかったもね。何万行とCを書いたのにポインタ の本当の意味の文法(シンタックスシュガー)を知ったのは10年くらいのちだよ!
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月22日(土) 12:03
by Math
また 多次元配列 については(前橋氏は)次のようにのべられています。
(過去ログに私の実例(ソース)がありますよ。
この件について詳述したサイトもありますよ。メモっておいたのだが今見つかりません(^^;)
”他にも、「宣言の形と使用時の形を似せる」という方針のために、 宣言の中の * と式の中の * とでは意味が全く異なるにも関らず 同じ記号が使われている。前者はポインタを意味し、後者は間接参照を 行なう演算子である。
また、この宣言の読み方を見ればわかるように、 C言語には多次元配列は存在しない。 int a[10][10]; という宣言は、多次元配列ではなく、 「配列の配列」を意味する。
# Cの規格書では、脚注に「多次元配列」という言葉が最初に出現し、
# それ以後はちょこちょこ使われているから、「多次元配列は存在しない」
# という言い方はきつ過ぎるかも知れませんが、Cの宣言を理解するには、
# そう考えた方が得なのは間違いないと思います。”
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月22日(土) 16:54
by ISLe
オフトピック
int a, b;
a = 1;
b = a + 1;
ここに出てくるaは全部意味が違うのにどうして同じ記号(記号なし表記)なの?教えて偉い人
その上で、C言語のポインタが宣言と演算子で同じ記号を使うことの何が問題なの?教えて偉い人
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月22日(土) 18:10
by Math
#9 にきしてある本ををお読みください
18刷まででたベストセラーであり 新版はC11 対応です
この本によると
Cは演算子が 多言語に比べ極端に多く しかもその優先順位が15段階もあります。
規格は BNF( Backs-Naur Form ) と呼ばれる記法により構文規則を定義しており 演算子の優先順位も構文規則のなかに含まれます。(とても素人にゃーよめませんよ)
このかたは自分で ライブラリをつくって ソフト会社で教えておられます。
私も C でライブラリを作ってコンピューター基板で制御するのが最初だった。
C は Unix 作るための 高級アセンブラーだった。それまでOSはアセンブラでしかつくられてなかった。
printf なんてCの規格にゃなかった。
著者によると 「Cの問題点 Cのいい加減さ を指摘しているため嫌っていると誤解されるが
Cは偉大な言語であり 長年現場で実用されてきた言語には それだけの実力がある」とかいてます。
いまやC は大型コンピューターから小型の制御基板まで広くつかわれ われわれは一部 パソコンにつかわれているものの話をしてるだけです。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月22日(土) 18:22
by Math
ところでアセンブラでブートストラップから書けるひといる(^^;
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月22日(土) 18:47
by Math
Cの本体は小さくて 表示 も 入力 もライブラリーです。
自分で一からつくると面白いですよ。(制御用マイコンがそうだね)
CでCを改良できるよ。まあ当たり前だけど。
ちなみに VB は VBをつかって 2千万行で 作り変えたよ
「Visual Basic 2015
2015年に.NET Framework 4.6とともに公開。Visual Studio 2015に同梱される。Roslynと呼ばれるコンパイラレイヤーにより、Visual C#と同等のIDE機能を備えるに至った」(ウキペディア より)
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月23日(日) 13:51
by かずま
ISLe さんが書きました: ↑4年前
オフトピック
int a, b;
a = 1;
b = a + 1;
ここに出てくるaは全部意味が違うのにどうして同じ記号(記号なし表記)なの?教えて偉い人
その上で、C言語のポインタが宣言と演算子で同じ記号を使うことの何が問題なの?教えて偉い人
出てくる場所によって意味の違いを認識できるから、
同じオブジェクトを指示(designate)する識別子 a は同じ記号でよい。
a はポインタではないので、オブジェクトを指す(point to)とか、
参照する(refer to)とは言わないことにします。
int a の場合、int の後なので、オブジェクトの宣言だと分かります。
a = 1 の場合、「=演算子」の左オペランドなので、左辺値として処理
します。オブジェクト a の値は使用しません。
a + 1 の場合、「+演算子」のオペランドなので、左辺値である a から
値を取り出して使用します。
「C言語のポインタが宣言と演算子で同じ記号を使うこと」に何の問題も
ないと私は思っています。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月23日(日) 14:13
by かずま
Math さんが書きました: ↑4年前
また、この宣言の読み方を見ればわかるように、 C言語には多次元配列は存在しない。 int a[10][10]; という宣言は、多次元配列ではなく、 「配列の配列」を意味する。
# Cの規格書では、脚注に「多次元配列」という言葉が最初に出現し、
# それ以後はちょこちょこ使われているから、「多次元配列は存在しない」
# という言い方はきつ過ぎるかも知れませんが、Cの宣言を理解するには、
# そう考えた方が得なのは間違いないと思います。”
Cの規格書には、多次元配列、n次元配列、3次元配列という用語が出てきます。
トピック「
3次元配列」の #4 に引用しました。
・C には、「配列の配列」はあるが、「多次元配列」はない。
というのは無理があると思います。
・C では、「多次元配列」があって、それは「配列の配列」で表現する。
と言えばよいと思います。
ところで、Cの規格書には「変数」という用語がほとんど出てこないことは
ご存知でしょうか?
JIS X 3010:2003 プログラム言語C - 日本工業規格の簡易閲覧 で
「変数」を検索すると 10個あることはありますが、
関数、定数、型、式、文、配列、ポインタなどの用語は何百何千と登場し、
目次や索引にも含まれているのに比べて、あまりにも少ないと思いませんか?
・C には、「変数」があって、規格書ではそれは「オブジェクト」と書かれている。
と言っていいと思います。
・C には、「オブジェクト」はあるが、「変数」はない。
とは誰も言いませんよね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月23日(日) 14:48
by かずま
Math さんが書きました: ↑4年前
"K&Rによれば、Cの宣言は、「変数が現われ得る式の構文を真似た(P.114)」 そうである。しかし、本質的に全く異なるものを無理に似せようとしたため、 結局わけのわからない構文になってしまっている。 「宣言の形と使用時の形を似せる」というのはC(およびCから派生したC++, Javaなどの言語)に特有の「変な構文」である。"
と前橋さんはかいておられます。
わけの分からない構文になったのは、
宣言時の形と使用時の形を似せたからではありません。
ポインタの「*」を前置にし、配列の [] と関数の () を後置にした
ことによるものです。
x : array [3] of pointer to function returning pointer to array [5] of char
x は、charの配列(要素数5)へのポインタを返す関数へのポインタの配列(要素数3)
ポインタの「*」が前置だと、char (*(*x[3])())[5];
ポインタの「^」が後置だと、char x[3]^()^[5];
これは、C を開発した Dennis M. Ritchie が後に反省点として挙げている点です。
The Development of The C Programming Language
コード:
char s[] = "abcdef";
for (char p^ = s; p^ != '\0'; p++)
putchar(p^);
後置でも何の問題もありません。
コード:
struct Point { int x, y; } a, p^:
p = a;
p^.x = 24;
p^.y = 32;
(*p).x と書くのは面倒で分かりにくいから
-> という演算子を用意し、p->x と書けるようにしました。
でも、後置ならご覧のように新しい演算子は要りません。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月23日(日) 21:00
by Math
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月24日(月) 08:23
by Math
#36 などは #9でPascal との比較をすればわかる等 #9のCの型モデルなどにに詳述してある。
Dennis M. Ritchie が後に作った言語にはPascal流に改良されている。
ところで Win32API の WINAPI は最初のころ PASCAL を使っていました。(いまでも使えます)
これは少し意味合いが違って関数の呼び出し 後処理がPascal風なのですね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月24日(月) 09:39
by Math
#9 の説明より
「メモリー上で一定の領域をしめ sizeof 演算子でサイズが特定できる型のことを、規格では
オブジェクト型 とよんでいる」
(^^;
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月24日(月) 09:54
by Math
Cかた は 次のように 分類できる
・オブジェクト型 (char int 配列 ポインタ 構造体 など)
・関数型
・不完全型( 構造体タグ など)
(#9より)
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月26日(水) 05:57
by ISLe
Math さんが書きました: ↑4年前
#9 にきしてある本ををお読みください
18刷まででたベストセラーであり 新版はC11 対応です
#9の
リンク先にこうあります。
こういった混乱の原因は、「ポインタはアドレスだ」ということを理解していないところにあるのではありません。本当の理由は、
・C の「奇怪な」宣言の構文
・配列とポインタの間の「妙な」交換性
にあるのです。
#10の
リンク先にこうあります。
C言語では、「ポインタが難しい」と良く言われますが、 実際に初心者がCを学習する過程を見ると、以下のことだけは すぐに理解しているようです。
「ポインタっつーのは、要するにアドレスのことなんだな」
ここまでは簡単、誰でもすぐに理解します。
# 大体、いつだって、低レベルな概念の方が理解が速いものです。
# アセンブラなんて、誰だってすぐに理解できますよね。
# それでまとまったプログラムを組むには別な才能が必要だ、
# というだけで。
問題はそこから先です。
これをわたしなりに整理してみて
初心者でも「ポインタはアドレスだ」というのはすぐに理解できる
→だから、ポインタは難しくない
→なのに、ポインタでハマる者があとを絶たない
→ポインタが分からないのは、「ポインタはアドレスだ」と理解できないのが理由ではなく、文法が奇怪であることが理由である
というふうに書いてあると理解しました。
そこで疑問が…
「ポインタはアドレスだ」ってどういうことすか。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月26日(水) 07:56
by Math
#9 の本と アセンブラでレジスターでデータであるメモリーを取り込む仕組みを理解すりゃ 字ずら通り(リテラルな)意味だとわかるはず。
この本は過去ログで何度も勧めてきたし その本をもっているはずの回答者も頓珍漢なことをいってるから
人間の思い込みはなかなか解けないみてえだな!
本代はやすいものだし著者が損はさせないといってるから買ってよむこと。厳命です(^^;
それでも不思議におもうならどんな点かこちらがしりたいね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月26日(水) 08:18
by Math
老婆心でいえば
#9では
CのまえねB をつくるときBCPL(ベーシック コンパイラーだったと思う)を参考にしていた
Bはスカラーしかなくポインター も アダレス カウンター も 整数型みたいなものも おなじ レジスターのおおきさで おなじだった。インクリメントすれば 1 アドレスすすんだわね。
それをCもうけついで sizeof だけすすめるようになったよね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月26日(水) 10:16
by Math
まあ初心者なみの説明ですがここは有名でっせ。
https://www.sejuku.net/blog/25094
いまはアマがプロを簡単に追い抜く時代です。スマホで幼稚園のこが音声検索やカメラで映像検索するし
私もスマホの音声検索を主に使うけどPCよりよっぽど便利よ。検索リテラシーいまは決めてですよね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月26日(水) 10:51
by usao
オフトピック
【世の中に「ポインタがわからん」という人々が溢れている】
という話が真なのであれば(個人的にはこれ系の話自体が偽なんじゃないかと疑ってるけど)
これからそうなるかもしれない対象たる初心者に向けた文章に
> 「ポインタはアドレスだ」
なんていう ふわっとした フレーズが
時に,「ポインタは難しいぞぉ!」だの「文法がどうのこうのっ!」だのいう刷り込み目的としか思えない謎の呪文句を添えた形で
存在したりしていることがそもそもの元凶なんじゃないか? とか思えてしまう.
(個人の感想です)
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月26日(水) 11:11
by usao
オフトピック
つまり,初心者をポインタで迷子にしないための方法として,以下が考えられる.
【「ポインタは難…」とか「Cの文法が…」とかいう話を展開しているような本なりサイトに出くわした場合,それは読まずに別のを探すこと】
という指針を前もって与える,という方法.
この篩はうまく機能する気がしてならない.
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月27日(木) 08:43
by ISLe
オフトピック
ポインタは、慣れるまで難しいのは事実で、ポインタをある程度扱うようになった、あるいは扱わざるを得なくなったヒトがハマるパターンも。
デバイスを扱うライブラリの初期化関数に、ダブルポインタで、デバイスを定義した構造体へのポインタを取得する、みたいなのがよくある。
そういうライブラリを初めて使うようなヒトが、ダブルポインタで宣言した変数を実引数としてしまうミスをしがち。
正しくは、シングルポインタで宣言した変数に、アドレス演算子付けたのを実引数にする。
深く考えず*や&を付けてしまう第一の壁。
*や&を付ける必要性に悩む第二の壁。
そこに「ポインタはアドレスだ」というフレーズの影響力。
オフトピック
ここの 補足:ポインタは配列より高速か? という見出し(タグがないけど)の文章がタイムパラドックス。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月27日(木) 11:17
by Math
#47
http://wisdom.sakura.ne.jp/programming/c/c25.html
のように具体例でしめしてね。
でないと#45、#46 でいわれていることに該当するようにみえまっせ。
抽象論でおどかすなよ!
それしマイナーなハードよりアセブラよりな内容でしょ。
#9に説明があるようにポインターをつかっても配列でも”最適化”されるから同じ事ならわかりやすくということ。
PDP-11の機械語の名残り 3項演算子 なんていまはつかわないだろ。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月27日(木) 17:23
by ISLe
オフトピック
Direct3DのCreateDeviceとか具体例に思い当たらなかったら突っかかってこないでほしいなあ。
Direct3Dは、マイナーなハードのアセンブラよりの内容だったかあ。
深く考えずに入門書のコードをコピペしてた人が、コピペをやめると途端にやらかすのを、けっこう見掛けるんですけどねえ。
オフトピック
オフトピック
江戸時代の手紙は、飛脚という職業の人が徒歩や馬で運んだらしいですよ。
なんで、自動車や電車を使わなかったんでしょうね。
C言語の最適化が大きなブームになっていたのは、わたしが20代のころでしたね。
DOSエクステンダで、コンパイラが潤沢なメモリを使えるようになったころからです。
当時、Cコンパイラは有償で高価だったので、無償で利用できるgccが人気でした。
マニア以外に浸透したのはウィンドウズ95の登場でプロテクトモードが当たり前になって以降ですかね。
K&Rが最初に出版されてから実に20年近く経ってのことです。
当時わたしはX68000でgccを使っていて、割と早くからコンパイラの最適化の恩恵にあずかってました。
配列演算とポインタ演算で同様のコードを生成するループ最適化の実装には感動しましたね。
逆に言えば、それ以前は、なかったわけですけどね。
いまはこうなんだから、最初からやっとけよってのは、かなり頭の悪い発言じゃあないですかね。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月27日(木) 18:44
by Math
そうだったのですか”愛する”さん(^^;
たしかシャープに X68000 のマシンがありましたね。
懐かしい!
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月28日(金) 23:32
by かずま
ISLe さんが書きました: ↑4年前
そこで疑問が…
「ポインタはアドレスだ」ってどういうことすか。
(1)「ポインタはアドレスだ」
(2)「ポインタは変数だ」
(3)「ポインタは型だ」
(4)「int は整数だ」
(5)「int は変数だ」
(6)「int は型だ」
(3) と (6) は誰でも正しいと思うのではありませんか?
(5) は変だと思いますよね。
int i = 3; という宣言があった時、
・i は int で、変数です。
・3 は int ですが、変数ではありません。定数です。
・i + 5 は int ですが、変数ではありません。+演算子の式です。
変数でない int があるのです。int は型ですから。
(2) も同じです。
int i = 3; int *p = &i; という宣言があったとします。
・p はポインタで、変数です。
・&i はポインタですが、変数ではありません。単項&演算子による式です。
i が静的変数(グローバル変数、または static がついた変数)の場合、
&i を「アドレス定数」ということが規格書に記述されています。
・p + 5 はポインタですが、変数ではありません。+演算子の式です。
変数でないポインタもあるのです。ポインタは型ですから。
(4) は「int は型だが、その保持する値は整数だ」という意味では正しい。
もちろん個数が無数である数学の整数とは異なりますが。
(1) は「ポインタは型だが、その保持する値は(ある変数の)アドレスだ」と
言えます。関数のアドレスは今は考えないことにします。
アドレスとは何か、というのは規格書には記述されていないのですが、
メモリのどの位置かを表す値であるということは認識されていると思います。
ポインタはアドレスを値として保持しますが、さらにメモリのその位置に
どんな型のオブジェクトがあるのかを確定できます。
int へのポインタ、char へのポインタ、double へのポインタなど。
アドレスには、その位置に何があるかという情報がないのが
ポインタとは異なることです。
なお、初心者が「ポインタはアドレスだ」とどういう意味で言っているのかは
私にはわかりません。
Re: 配列へのポインタ (*a)[10] について
Posted: 2019年6月29日(土) 08:23
by Math
ポインターは基本型ではない。
K&R に詳述されているように
基本型を先頭に派生型を再帰的にくりかえし 無限の型を作り出すことができる。
http://www.officeuchida.com/pdt/pdtsamp2.html
Re: 配列へのポインタ (*a)[10] について
Posted: 2020年4月07日(火) 21:01
by あたっしゅ
>ところでアセンブラでブートストラップから書けるひといる(^^;
川合秀美さんの x86 で OS を作る本(複数)を参考にして、8086 の Real モードで IPL、書いてますよ。