配列へのポインタ (*a)[10] について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
YUKI007BKB
記事: 6
登録日時: 1週間前

配列へのポインタ (*a)[10] について

#1

投稿記事 by YUKI007BKB » 1週間前

配列へのポインタ 「 (*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;
}
どのようなときに配列へのポインタは効力を発揮するのか,できればそのサンプルプログラムなども教えていただけると幸いです.

Math

Re: 配列へのポインタ (*a)[10] について

#2

投稿記事 by Math » 1週間前

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] のようにします。

Math

Re: 配列へのポインタ (*a)[10] について

#3

投稿記事 by Math » 1週間前

引数配列は正書法であるポインターに読み替えられます

int x[5][10];

int x[][10];



int (*x)[10]; シンタックスシュガーです。

https://ja.wikipedia.org/wiki/%E7%B3%96 ... B%E6%96%87

Math

Re: 配列へのポインタ (*a)[10] について

#4

投稿記事 by Math » 1週間前

「訂正」

int (*x)[10]; の シンタックスシュガーです。

Math

Re: 配列へのポインタ (*a)[10] について

#5

投稿記事 by Math » 1週間前

「訂正」

int (*x)[10]; の シンタックスシュガーです。

Math

Re: 配列へのポインタ (*a)[10] について

#6

投稿記事 by Math » 1週間前

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
続行するには何かキーを押してください . . .

Math

Re: 配列へのポインタ (*a)[10] について

#7

投稿記事 by Math » 1週間前

C言語はあまり気のきいた言語ではありません。
配列を渡しても先頭アドレスが渡るだけで要素数さえわたりません。

しかし 人間に分かり易いシンタックスシュガーを使ってもコンパイラが最適化してくれるので
昔ほどポインターを駆使する必要はありません。

しかし文法がむつかしいのでこうゆう場合悩ませれますよね。

Math

Re: 配列へのポインタ (*a)[10] について

#8

投稿記事 by Math » 1週間前

C言語には本当の意味の2次元配列は存在しません。

2次元配列にみえるのは配列の配列です。
従って2次元配列を1次元配列として読んだり
1次元配列を多次元配列のようにできますよ。

Math

Re: 配列へのポインタ (*a)[10] について

#9

投稿記事 by Math » 1週間前

この本が唯一の正しいC言語の本だとおもいます。

疑問を持たれたら読んでみるといいですよ!
http://kmaebashi.com/seiha2/index.html

ーーーーーーーーーーーーーーーーーーーーーーーーーー
この本は、2001年の発売以来たいへんご好評をいただいた『C言語 ポインタ完全制覇』の改訂版です。

旧版は、最終的に第18刷を数えるロングセラーとなりました。読んでくださる皆様のおかげです。

今回の版では、時代に合わせたり、旧版では不足していたと思われる記述を補ったりという加筆・修正を行いました。
------------------------------------------------------------------------


Math

Re: 配列へのポインタ (*a)[10] について

#11

投稿記事 by Math » 1週間前

式中において [] は 配列の意味ですらない。

したがってつぎのプログラムは正しく 正常に実行される(^^;

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] と同じですね。

Math

Re: 配列へのポインタ (*a)[10] について

#12

投稿記事 by Math » 1週間前

詳細は過去ログに何度もかいたので検索してください。

Math

Re: 配列へのポインタ (*a)[10] について

#13

投稿記事 by Math » 1週間前

a[ i ] は *(a+i) のシンタックス・シュガーであり *(i+a) つまり i[a] と同じですね。


( a[ i ] はスペースを省くと イタリック体 指示とみなされ きえちゃうんですね(^^;  )

かずま

Re: 配列へのポインタ (*a)[10] について

#14

投稿記事 by かずま » 1週間前

YUKI007BKB さんが書きました:
1週間前

コード:

void foo(int (*x)[10]){
  (*x)[4]=40;
}
この関数の仕様は、
「要素数 10個の配列の先頭から 4つ先の要素を 40に変更する」
ですね。
呼び出し側が間違って、要素数が 10でない配列のアドレスを渡そうと
するとコンパイルエラーになり、間違いを検出できます。メリットです。
要素数が 10でない配列には使えません。デメリットです。
YUKI007BKB さんが書きました:
1週間前

コード:

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 はポインタ

YuO
記事: 934
登録日時: 8年前
住所: 東京都世田谷区

Re: 配列へのポインタ (*a)[10] について

#15

投稿記事 by YuO » 1週間前

YUKI007BKB さんが書きました:
1週間前
どのようなときに配列へのポインタは効力を発揮するのか,できればそのサンプルプログラムなども教えていただけると幸いです.
明示的に配列へのポインタを記述する必要は、ほぼないと思います。

多次元配列を引数にとるときに、そのまま書いたもの

コード:

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

Re: 配列へのポインタ (*a)[10] について

#16

投稿記事 by Math » 1週間前

わけのわからんことをごたごたいうと まさに初心者のかたを混乱させるだけなので

式の中 とくに関数の引数のときに ポインターで受けるのがC言語流のやりかたで

void foo(int x[10]) は
void foo(int *x) となる

void foo(int x[][10]) は
void foo(int (*x)[10]) となる 

これで十分だよね!

( 最初は納得しにくいですよね。紹介した本をかいたひとも3年かかってわかったそうですよ(^^;  )

Math

Re: 配列へのポインタ (*a)[10] について

#17

投稿記事 by Math » 1週間前

いまは 普通に配列流に書けばいいですよ。

私は大型機で何万行というC言語のプログラムを書いたけどポインタを意識したことはありませんよ。

テリー・サバラン

Re: 配列へのポインタ (*a)[10] について

#18

投稿記事 by テリー・サバラン » 1週間前

>配列へのポインタを使うメリットがわかりませんでした.

メリットは、処理時間とシステム資源(メモリ)の節約。



以前、アプリケーションソフトを作った時に、構造体配列の並べ替えをしたことがありました。

その構造体の1要素はデータ量が大きいため、実際に構造体データを並べ替えるのではなく、構造体の配列要素へのポインタ配列を並べ替えることで、処理時間を短縮できました。

また、例えば10パターンで並べ替えた結果が必要な場合に、もとの構造体配列を9回コピーして10個にし、そのデータを並べ替えるよりも、構造体配列へのポインタ配列を10個作成し、そのポインタ配列を並べ替える方が効率的です。

Math

Re: 配列へのポインタ (*a)[10] について

#19

投稿記事 by Math » 4日前

#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の文法は、一見首尾一貫しているようで、実は数多くの例外が存在する。

[上記の本、サイト参照]

以下時間の取れる時にこの場合の説明を記す。

YUKI007BKB
記事: 6
登録日時: 1週間前

Re: 配列へのポインタ (*a)[10] について

#20

投稿記事 by YUKI007BKB » 3日前

Math さんが書きました:
1週間前
わけのわからんことをごたごたいうと まさに初心者のかたを混乱させるだけなので

式の中 とくに関数の引数のときに ポインターで受けるのが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言語の教養として大変興味深いものでした.
また自分が質問していて,お時間がありましたらお力を貸していただけると幸いです(;_;)
改めまして,本当にありがとうございました!

ISLe
記事: 2640
登録日時: 8年前
連絡を取る:

Re: 配列へのポインタ (*a)[10] について

#21

投稿記事 by ISLe » 3日前

オフトピック
Cの文法は、意味に一貫性を持たせているのではなくて、表記に一貫性を持たせている。
K&Rにも、記述や表現の一貫性というふうに、書いてある。

表記に一貫性を持たせれば、宣言するときと式の中で使うときとで意味が逆になるのは当然のことじゃあないのか。

例えばポインタを
int >a;
というふうに宣言して
<a で間接参照するようにしたら分かりやすいか?
そうは思わない。

うっかり>aと書いた場合どうしたらいい?
むしろうっかりミスの機会が増える。
宣言では>しか使わないし、間接参照では<しか使わない。
だったら同じ記号でいいじゃないか。
逆に書いてしまうミスも防げる。
オフトピック
アセンブラだとアドレスが1バイトずつ進むのが当たり前のように書いてるヒトって8ビットCPUのころに触っただけなのかなって思ってしまうけど、実際どうなんだろう。

かずま

Re: 配列へのポインタ (*a)[10] について

#22

投稿記事 by かずま » 2日前

#2 に見当違いなことが書かれているのでちょっと指摘します。
Math さんが書きました:
1週間前
1番目のプログラムは大幅に間違えています。
どこにも間違いはありません。
Math さんが書きました:
1週間前
プロトタイプ宣言には仮変数名が必要です。
「仮変数」というのは「仮引数」の間違いではありませんか?

プロトタイプ宣言には仮引数名はあってもいいし、なくても構いません。
 void foo(int (*x)[10]);
 void foo(int (*)[10]);
どちらでもよい。
Math さんが書きました:
1週間前
実引数 int (*x)[10]) は 2次元配列 x[][10] に相当します。
int (*x)[10] は実引数ではありません。仮引数です。
実引数は、関数呼出し元の引数です。型名の int があってはいけません。
例えば #1 の1番目のプログラムの f(&a); の &a です。

仮引数 int (*x)[10] に対して、実引数が常に 2次元配列である必要はありません。
int a[10]; という 1次元配列があれば、&a という実引数は正しい。

アバター
usao
記事: 1529
登録日時: 6年前

Re: 配列へのポインタ (*a)[10] について

#23

投稿記事 by usao » 2日前

既に述べられていることの繰り返しになるが,

コード:

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];  };
みたいな専用のものを使うことを強要することとほぼ変わらない程度だろう.

返信

“C言語何でも質問掲示板” へ戻る