関数に配列のアドレスを渡すという考え

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

関数に配列のアドレスを渡すという考え

#1

投稿記事 by wrsvrsk » 1年前

コード:

#include <stdio.h>
void func(int arg[]) ;   //プロトタイプ宣言

int main()
{
  int a[] =                 //配列の宣言
  {
  0, 1, 2, 3, 4             //初期化している
  } ;
  int count ;

  for (count = 0 ; count < 5 ; count ++)
  {                         //配列の中身を画面に出力
   printf("%d ", a[count]) ;
  }
   printf("\n") ;            //たんに改行している
 
   func(a) ;                 //関数に配列を渡している

  for (count = 0 ; count < 5 ; count ++)
  {                          //配列の中身を再度画面に出力
   printf("%d ", a[count]) ;
   }
}

void func(int arg[])
{
  arg[0] = 10 ;
  printf("呼ばれた関数では配列の0番目は%d\n", arg[0]) ;
}  
 
C言語改訂版 2 はじめて学ぶCの仕組みのp97のコードです
func(a) ; これは配列aのアドレスを渡すということですが、
void func(int arg[])
配列argの型はintと定義されているのに、そこにint型配列aのアドレスを渡す、この2つは違うものでつながりが見えなくなってしまうのですが?

ちなみに配列aの先頭アドレスは私のPCでは642296(10進数)と出て来ました。
ちなみに

コード:

#include <stdio.h>

void increment(int *p)   //ポインタ変数に格納されている「場所」が渡される
{
  (*p)++ ;               //「場所」の中の値をインクリメントしている
}

int main()
{
  int num ;

  num = 10 ;              //通常の変数に10を代入している
  increment(&num) ;        //インクリメント関数に、変数の「場所」を渡す

 printf("num=%d", num) ;  //11が表示される
}
 
であれば(&num)はint型変数numのアドレス
(int *p)これはint型の変数のアドレスを入れるポインタ変数p, p = &numとして流れが分かるのですが
上記の例で言うとarg[] = a[] になる筈ですが、(int arg[])
にa[]のアドレス642296を渡すと何故arg[] = a[] になるのか?arg[642296]ではないだろうし、強引に配列argの最初のアドレスは642296だ、と言うということでしょうか?
しかしどうしたらそう言えるのか分かりません。
すいません、頭がごちゃごちゃしていて説明しにくいです。

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

Re: 関数に配列のアドレスを渡すという考え

#2

投稿記事 by usao » 1年前

後半の方は何をおっしゃっておられるのかよくわかりませんが…

コード:

void func(int arg[]){ ... }
は
void func( int *arg ){ ... }
と同じ.

コード:

関数func内の
arg[0] = 10;
は
*( arg + 0 ) = 10;
と同じ.

コード:

func( a );
なる呼び出しは,
func( &( a[0] ) );
と同じ.
纏めると,

コード:

void func( int *arg )
{
  *( arg + 0 ) = 10;
}

int main()
{
  int a[] = { 0,1,2,3,4 };
  func( &( a[0] ) );
}
といった感じです.この表記なら解りやすいですか?

Math

Re: 関数に配列のアドレスを渡すという考え

#3

投稿記事 by Math » 1年前

Cの配列の説明については本当のことを詳しく説明した本、サイトが殆どありません。(^^;

コード:

  int a[] =                 //配列の宣言
  {
  0, 1, 2, 3, 4             //初期化している
  } ;
  
これは 配列 a[5] 宣言しているので問題はないでしょう。

しかしそれ以外の添字演算子 [] 配列とは無関係です。

a[count] という書き方は
*( a + count )

という書き方の簡便記法であり それ以外の意味は 全く 全然 どこにもありません!

したがって a[count] は count[a] と同じである。

また関数の仮宣言の宣言の場合に限り 配列の宣言はポインターに読み替えられます。

void func(int arg[]) は  void func( int *arg )
にコンパイラにより読み替えられます。

void func(int arg[5]) のように要素数が入っていても無視されます。

注意しないといけないのは int arg[] が int *arg と同じ意味をもつのは Cの文法のなかで唯一このケースだけである ということです。

-----
p = & a[0] は  p=a
と書いてもよいことになっています。

Math

Re: 関数に配列のアドレスを渡すという考え

#4

投稿記事 by Math » 1年前

したがって

c1.c

コード:

#include <stdio.h>
void func(int arg[]) ;   //プロトタイプ宣言

int main()
{
  int a[] =                 //配列の宣言
  {
  0, 1, 2, 3, 4             //初期化している
  } ;
  int count ;

  for (count = 0 ; count < 5 ; count ++)
  {                               //配列の中身を画面に出力
    printf("%d ", *(a + count) );//--- この形に読み替えられる ---
  }
  printf("\n") ;               //たんに改行している


  func(a) ;                //関数に配列を渡している

  for (count = 0 ; count < 5 ; count ++)
  {                           //配列の中身を再度画面に出力
    printf("%d ", count[a]) ; //--- a[count] を逆にした ---
  }
}

void func(int *arg)
{
  int count ;

  arg[0] = 10 ;
  printf("呼ばれた関数では配列の0番目は%d\n", arg[0]) ;

  for (count = 0 ; count < 5 ; count ++)
  {                                      //配列の中身を  func  内で画面に出力
     printf("%d ", count[arg]) ;        //--- arg[count] を逆にした ---
  }
  printf("----- func 内 -----\n");

}  
 
c.bat

コード:

rem コンパイル後リンク
cl /TC c1.c
dir *.exe
pause

rem 実行結果
c1.exe

pause
http://www2.koyoen.birdview.co.jp/~abcxyz/z0830.png

Math

Re: 関数に配列のアドレスを渡すという考え

#5

投稿記事 by Math » 1年前

#3 [訂正]
>また関数の仮宣言の宣言の場合に限り 配列の宣言はポインターに読み替えられます。

また関数の仮引数の宣言の場合に限り 配列の宣言はポインターに読み替えられます。

Math

Re: 関数に配列のアドレスを渡すという考え

#6

投稿記事 by Math » 1年前

どうしてこうなったかというと むかしは ポインター演算を駆使したほうが高速なプログラムをかけたからです。

しかしいまは 最適化技術もすすみ 配列のごとく かいても 高速なので 人間に分かりやすい書き方で十分です。

このように人間にわかりやすくするためだけに(甘い)導入された機能を シンタックスシュガー
syntax sugar , or ,syntactic sugar といいます。

https://ja.wikipedia.org/wiki/糖衣構文

でも いざというとき 知らないと ”コマッチャウナー” になります(^^;

Math

Re: 関数に配列のアドレスを渡すという考え

#7

投稿記事 by Math » 1年前

???
>int型の変数のアドレスを入れるポインタ変数p, p = &numとして流れが分かるのですが


B言語の時代はアドレスと ポインターは同じ(整数型)でした。

C言語ではポインターとアドレスは違います。 型が増えたため siseof(型名) (サイズ情報)が
ポインターにはひつようです。

Cではアドレスは ポインターの先頭をさし ポインターに +1 すると サイズ分増える! のですよ。

int は普通四バイト(機種、コンパイラ 依存)ふえますよ。

wrsvrsk
記事: 11
登録日時: 1年前

Re: 関数に配列のアドレスを渡すという考え

#8

投稿記事 by wrsvrsk » 11ヶ月前

http://kmaebashi.com/programmer/pointer.html

void func(int *a){}とvoid func(int a[]){}

は全く同一である。引数 a は、配列に見えるが、実は単なるポインタの 宣言である(a++などの操作も可能である)。要素数は書いても無視されるので、 a[] と書いても、a[10] と書いても、a[100]と書いても全く同一の意味となる。

Cの文法中で、配列の「宣言」がポインタに読み換えられるのは、 唯一このケースだけである。つまり、int *a と int a[] が同一の意味となるのは、 「関数の仮引数」という限られたケースにおいてのみである。
----
みなさまたくさんのレスをありがとうございます、皆様の解説と上記のサイトを読み
ぐんと理解が進みました。
配列名を書いてもそれは配列のポインタとなる、という説明はあっても、例えば
int a[]と[]が付いているなら、これは配列aで要素の数値がintであるとも読めますよね。
例え[]があってもポインタなんだとワンクッションあればよいのに、そういった説明がまったくありません。int a [] はaという配列の要素に整数型を入れるという意味も教えられている以上、はっきりそれとは違うケースだとの説明を強く望むところです。

まだポインタとアドレスのはっきりした違いが分かってないのでそこはご容赦願いつつ、、
コードに戻って
func(a) ;
void func(int arg[])
{
arg[0] = 10 ;
仮引数で使われているarg[]の[]は無視されるので、これは配列aのポインタを入れる変数argである。
仮にここでポインタを642296としますと、argには642296の数字が入っていると
arg[0] = 10 ; これについては

以下Math様の書き込みを参考にして
ーーーーーー
a[count] という書き方は
*( a + count )

という書き方の簡便記法であり それ以外の意味は 全く 全然 どこにもありません!

したがって a[count] は count[a] と同じである。
ーーー
arg[0]という書き方は*(arg + 0)という書き方の勘弁記法である。
そこでまたお尋ねしたいのですが、arg[0] = 10 ;は配列argの要素番号0番目に10を代入せよという解釈もなりたつと思うのですが、配列のポインタを受け取る場合などケースによって*(arg + 0)と
解釈するということでよろしいのでしょうか?argには確かにポインタが入ってますので
arg(=642296)[0]はargという配列ではなく*(642296 + 0]と解釈すべきですよね?

Math

Re: 関数に配列のアドレスを渡すという考え

#9

投稿記事 by Math » 11ヶ月前

>>>
arg[0]という書き方は*(arg + 0)という書き方の勘弁記法である。
そこでまたお尋ねしたいのですが、arg[0] = 10 ;は配列argの要素番号0番目に10を代入せよという解釈もなりたつと思うのですが、配列のポインタを受け取る場合などケースによって*(arg + 0)と
解釈するということでよろしいのでしょうか?argには確かにポインタが入ってますので
arg(=642296)[0]はargという配列ではなく*(642296 + 0]と解釈すべきですよね?
>>>
その通りです。

Math

Re: 関数に配列のアドレスを渡すという考え

#10

投稿記事 by Math » 11ヶ月前

しかし アドレスとポインターは 違う ので 厳密には 微妙にちがいます。

プログラム上そういうふうに代入出来ないですが 考えかたはその通りです。

int func(int a[]) と
int func(int a[5]) は

int func( int *a)

シンタックスシュガーです。直ちにこの形に読み替えられます。(要素数は渡らないので 別途 引数で渡す等 対策が必要ですよ)

Math

Re: 関数に配列のアドレスを渡すという考え

#11

投稿記事 by Math » 11ヶ月前

コード:

#include <stdio.h>
void func(int arg[], int n) ;   //プロトタイプ宣言

int main()
{
  int a[] =                 //配列の宣言
  {
  0, 1, 2, 3, 4             //初期化している
  } ;

  func(a,5) ;               //関数に配列を渡している

}

void func(int *arg,int n)
{
  int count ;

  *(arg + 0) = 10 ;
  printf("呼ばれた関数では配列の0番目は%d\n", arg[0]) ;

  for (count = 0 ; count < n ; count ++)
  {                                      //配列の中身を  func  内で画面に出力
    printf("%d ", *(arg + count) );
  }

  printf("\n----- func 内 -----\n");

}  
 
http://www2.koyoen.birdview.co.jp/~abcxyz/z0831.png

wrsvrsk
記事: 11
登録日時: 1年前

Re: 関数に配列のアドレスを渡すという考え

#12

投稿記事 by wrsvrsk » 11ヶ月前

私、本が説明不足と不満を書いてしまいましたが、その後、10ページほど読み進めると

a[5] = 10 ; コンパイラはこのような行をみつけると

*(a+5) = 10 ; 内部的にこのように置き換える

と書いてありました。初学者にとって難しいのは、読み進める前に頭がごちゃごちゃして嫌になってしまうことですね。甘えを言わせてもらえば、スムーズにつなげられるように
してもらえると良いですが、私の間違いは間違いとして訂正いたします。

結局のところa[5]のaには配列aの先頭アドレス、例えば642296が代入されるのでa[5] も 5[a]もpc的には アドレス(a+5) とアドレス(5+a)の中身を言ってる *(a(642296) + 5) を言ってるので、同じ値を指しているということになるみたいですね。

さらに読み進めると2次元配列の全体を渡す時、ポインタとして受けることが出来ないとあります
void func(int arg[]) ;ではダメで
void func(int arg[][5]) ;
と要素数は書かないといけないようですね
ポインタとして受けることが出来ないという意味がいまいちわからなく
void func(int *arg)の式ではダメということでしょうか?
しかし(int arg[][5])(これはcharの誤り?) ;もchar color[3][5] = {"Blue", "Gray", "Pink"} ;の先頭のアドレスを入れているのならポインタで受けていると言えないのですかね

かずま

Re: 関数に配列のアドレスを渡すという考え

#13

投稿記事 by かずま » 11ヶ月前

wrsvrsk さんが書きました:
11ヶ月前
結局のところa[5]のaには配列aの先頭アドレス、例えば642296が代入されるので
a に、a の先頭アドレスが代入される?
代入演算子 = を使わない限り、代入は実行されません。

ポインタの値(アドレス)は、"%d" ではなく、"%p" で表示しましょう。

コード:

#include <stdio.h>

int main(void)
{
	int a[6] = { 1, 2, 3, 4, 5, 6 };
	*(a + 3) = 20;  // a[3] = 20; と同じ
	a[5] = 10;      // *(a + 5) = 10; と同じ
	for (int i = 0; i < 6; i++)
		printf("&a[%d]: %p,  a[%d]: %d\n", i, &a[i], i, a[i]);
	printf("    a: %p\n", a);   // a は 先頭要素へのポインタ。&a[0] と同じ
	printf("   &a: %p\n", &a);  // &a は 配列全体へのポインタ
	printf("  a+1: %p\n", a + 1);   // a + 1 は、&a[1]
	printf(" &a+1: %p\n", &a + 1);  // &a + 1 は、&a[6] の次のアドレス
}
実行結果

コード:

&a[0]: 00D8FA50,  a[0]: 1
&a[1]: 00D8FA54,  a[1]: 2
&a[2]: 00D8FA58,  a[2]: 3
&a[3]: 00D8FA5C,  a[3]: 20
&a[4]: 00D8FA60,  a[4]: 5
&a[5]: 00D8FA64,  a[5]: 10
    a: 00D8FA50
   &a: 00D8FA50
  a+1: 00D8FA54
 &a+1: 00D8FA68
int a[6] = { 1, 2, 3, 4, 5, 6 }; の宣言時、
a の値は、{ 1, 2, 3, 4, 5, 6 }。
a の型は、int [6]。すなわち、int の配列(要素数 6)。

しかし、式の中で a を参照するとき、a は次のように変換されます。
a の値は、配列の先頭要素である a[0] のアドレス。
a の型は、「配列の先頭要素である a[0] の型である int」へのポインタ。
a と &a[0] は、値も型も同じです。

式の中で a[ i] は *(a + i) と同じです。
配列 a は、配列の先頭要素へのポインタに変換され、
+i で i個先の要素を参照します。

&a の場合、a は a[0] へのポインタに変換されません。
&a の値は、配列 a 全体のアドレスで、a[0] のアドレスと同じになります。
&a の型は、int * ではなく、int (*)[6] です。
wrsvrsk さんが書きました:
11ヶ月前
さらに読み進めると2次元配列の全体を渡す時、ポインタとして受けることが出来ないとあります
いいえ、ポインタとして受け取ります。
2次元配列を関数に渡す場合は次のようになります。

コード:

#include <stdio.h>

void func(int (*a)[4])  // int a[][4] または int a[3][4] とも書いても同じ
{
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++)
			printf("%6d", a[i][j]);
		putchar('\n');
	}
}

int main(void)
{
	int a[3][4] = {
		{ 100, 101, 102, 103 },
		{ 110, 111, 112, 113 },
		{ 120, 121, 122, 123 },
	};
	func(a);
}
宣言時の a の値は、12個の整数。a の型は、int [3][4]。
a は、要素数 3 の配列で、要素の型は int [4]。

func(a); の実行時、配列 a は、先頭要素 a[0] へのポインタに変換されます。
a[0] の型は int [4] なので、
a の型は a[0]全体へのポインタとなり、それは int (*)[4]。
a の値は、a[0] のアドレスです。

wrsvrsk
記事: 11
登録日時: 1年前

Re: 関数に配列のアドレスを渡すという考え

#14

投稿記事 by wrsvrsk » 11ヶ月前

ご説明ありがとうございます
>a の型は、int [6]。すなわち、int の配列(要素数 6)。
テキストではintの部分のみを型と教えていますが、要素数の部分も型と言えばそうかもしれませんね

printf(" a+1: %p\n", a + 1); // a + 1 は、&a[1]
printf(" &a+1: %p\n", &a + 1); // &a + 1 は、&a[6] の次のアドレス

これの結果が違うところがミソのようですね

>&a の値は、配列 a 全体のアドレスで、a[0] のアドレスと同じになります。
配列全体ですので、次を指定すると、次のアドレスではなくて配列の外のアドレスになってしまうということですかね

>int (*)[6]
int (a*)[6] これはあるサイトでは「 a は、int[6]型の配列を指すポインタです。」
とありますね
https://detail.chiebukuro.yahoo.co.jp/q ... 2119771142
まあ頭がこんがらがってますので、そんなもんなんだと憶えておきます


>いいえ、ポインタとして受け取ります。
2次元配列を関数に渡す場合は次のようになります。

テキストには、なぜか考えると、かなりややこしいのでこんなもんなんだと覚えてしまうのが良いとありますが、まあとにかく実際はポインタを渡しているのだと理解しておきます。
>int (*a)[4]
上にあるとおり、これが2次元配列のポインタを受け取る時の式なんですね
(*a)と書くとこれは、ポインタそのものを指すということなんでしょうか?
(*a)で場所の中を示す場合もあるので、なんだかややこしいですね

返信

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