合計 昨日 今日

配列とポインタの違いに関して

[このトピックは解決済みです]

フォーラムルール
フォーラムルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Name: pocket
[URL]
初心者(8,004 ポイント)
Date: 2017年11月15日(水) 02:52
No: 1
(OFFLINE)

 配列とポインタの違いに関して

お世話になっております。
今回は配列とポインタの違いに関して疑問点があり、
質問させて頂きました。

私は配列とポインタは同じようなものだと認識していました。
例えば、以下のようなコードを考えます。

コード[C++]: 全て選択
1
2
char buff1[10];
char * buff2 = malloc(sizeof(char)*10);


buff1とbuff2は要素の参照の仕方に違いはあれど、
基本的には同じものだと思っています。
例えば、char型のポインタを引数にとるfgets関数などにchar型の配列を指定しても動きます。
buff1は配列の先頭の要素の番地が、buff2はchar型の要素の最初の番地が格納されているという点でも、
同じだと思っていました。

今回疑問に思った点は、メモリの解放に関してです。
通常ポインタはfree関数でメモリを開放するかと思います。
しかし、C言語ではあまり配列のメモリを解放するということを聞きません。
これはなぜでしょうか?
C++に関してはdelete関数というものがありますが、C言語には無いように思います。

今回作成していたプログラムで、mallocの部分で実行時エラーが起きました。
少し調べた結果、一度mallocでメモリを確保して、free関数で解放し、
再度mallocで以前に使用したメモリ領域を跨ぐように確保しようとすると、実行時エラーが起きているような感じでした。
(この解釈が正しいかはわかりません)
そこで、一部をmallocではなく、配列に変更すると実行時エラーが発生しなくなりました。
なので、配列とポインタではメモリの処理に関して違いがあるのかなと思った次第です。

噂で聞いたのですが、mallocで格納したポインタは連続したメモリ領域が確保されるが、
配列は連続していない(ばらけている?)領域がとられると聞きましたが、今回の件に関係しているのでしょうか?

質問点をまとめますと、
・配列で確保したメモリ領域は明示的に解放しなくていいのか(C言語の場合)
・mallocでメモリを確保した場合と配列でメモリを確保した場合の、具体的な違いは何か?

ご教授頂ければ幸いです。
よろしくお願いいたします。

Name: かずま
[URL]
Date: 2017年11月15日(水) 04:57
No: 2
(OFFLINE)

 Re: 配列とポインタの違いに関して

pocket さんが書きました:私は配列とポインタは同じようなものだと認識していました。
例えば、以下のようなコードを考えます。
コード[C++]: 全て選択
1
2
char buff1[10];
char * buff2 = malloc(sizeof(char)*10);

buff1とbuff2は要素の参照の仕方に違いはあれど、
基本的には同じものだと思っています。

同じものではありません。
  • buff1 = buff2; // エラーになる
  • buff2 = buff1; // 正しい
  • sizeof(buff1) == 10
  • sizeof(buff2) == 4 (処理系によっては 2 や 8 などの場合もある)
  • &buff1 は、配列全体(10バイトの領域)へのポインタ
  • &buff2 は、ポインタ buff2 へのポインタ(10バイトの領域へのポインタではない)
ポインタは配列を指す場合もありますが、
単独の変数を指す場合もあります。
後者の場合、要素は存在しません。
前者の場合、配列を指しているのだから
その配列の要素は存在します。そして、
要素の参照の仕方は、
buff1[i], *(buff1+i), buff2[i], *(buff2+i)
で、すべて正しいものですから、
要素の参照の仕方に違いはありません。

pocket さんが書きました:例えば、char型のポインタを引数にとるfgets関数などにchar型の配列を指定しても動きます。
buff1は配列の先頭の要素の番地が、buff2はchar型の要素の最初の番地が格納されているという点でも、
同じだと思っていました。

buff2 には malloc で確保した領域の先頭アドレスが格納されていますが、
buff1 は、配列の先頭要素のアドレスという値に変換されるだけで、
buff1 自身にアドレスが格納されているわけではありません。

配列は、次の 2つの場合を除いて、式の中で、
先頭要素へのポインタの値に変換されます。
  • sizeof のオペランド
  • 単項&演算子のオペランド
したがって、buff2 = buff1; が正しくなり、
fgets の第1引数に buff1 を渡すこともできるのです。

pocket さんが書きました:今回疑問に思った点は、メモリの解放に関してです。
通常ポインタはfree関数でメモリを開放するかと思います。

free でメモリを解放するのは、ポインタが
malloc で確保された領域を指している場合だけです。
ポインタが配列を指している場合や単独の変数を
指している場合は、free で解放しません。

pocket さんが書きました:しかし、C言語ではあまり配列のメモリを解放するということを聞きません。
これはなぜでしょうか?

配列は、malloc で確保された領域ではないからです。

pocket さんが書きました:C++に関してはdelete関数というものがありますが、C言語には無いように思います。

delete は、関数ではなく演算子です。
new演算子で確保された領域を deleteで解放します。
Cには、new と delete は無く、malloc と free を使用します。

pocket さんが書きました:今回作成していたプログラムで、mallocの部分で実行時エラーが起きました。
少し調べた結果、一度mallocでメモリを確保して、free関数で解放し、
再度mallocで以前に使用したメモリ領域を跨ぐように確保しようとすると、実行時エラーが起きているような感じでした。
(この解釈が正しいかはわかりません)
そこで、一部をmallocではなく、配列に変更すると実行時エラーが発生しなくなりました。
なので、配列とポインタではメモリの処理に関して違いがあるのかなと思った次第です。

今回作成したプログラムというのを
見ないことには何も言えません。

pocket さんが書きました:噂で聞いたのですが、mallocで格納したポインタは連続したメモリ領域が確保されるが、
配列は連続していない(ばらけている?)領域がとられると聞きましたが、今回の件に関係しているのでしょうか?

配列の要素は連続しています。
char buff1[10], buff2[20]; のように
2つの配列があった時、それらが連続して
いるかどうかは不明です。

pocket さんが書きました:質問点をまとめますと、
・配列で確保したメモリ領域は明示的に解放しなくていいのか(C言語の場合)
・mallocでメモリを確保した場合と配列でメモリを確保した場合の、具体的な違いは何か?

関数またはブロック( { } のこと) の中で確保された
変数は関数またはブロックを出るときに解放されます。
コード[C]: 全て選択
1
2
3
4
5
6
void func(int x)
{
    int a;
    char b[10];
    // x, a, b に関する処理
} // ここで、x も a も b も解放される

グローバル変数や static を指定した変数は、静的に確保され、
プログラムの実行終了時に解放される。
関数やブロックの中で宣言された変数は、自動的に確保され、
関数やブロックから出るとき自動的に解放される。
malloc で確保された領域は、free を実行するまで解放されない。

Name: pocket
[URL]
初心者(8,004 ポイント)
Date: 2017年11月15日(水) 12:09
No: 3
(OFFLINE)

 Re: 配列とポインタの違いに関して

かずまさん

詳細に解説頂きありがとうございます.
配列とポインタは同じものではなかったのですね.
ロベール本では同じものであるように解説されていましたので,
勘違いしていました.
かずまさんの回答に関して何点かお伺いしたいことがございます.

【疑問点1】
かずま さんが書きました:&buff1 は、配列全体(10バイトの領域)へのポインタ
&buff2 は、ポインタ buff2 へのポインタ(10バイトの領域へのポインタではない)


上記に関して,配列全体へのポインタとはint型のポインタのような感じでしょうか?
int型のポインタとは以下のようなものをイメージしています.
コード[C++]: 全て選択
1
2
int a=10;
int * ptr_a=&a;

int型のポインタは32バイトの領域のブロックを指していると思います.
同じようにbuff1はmallocで10バイトの領域のブロックが取られたものであるので,
新しく10バイトの型が作られたのだという理解でいいでしょうか?

【疑問点2】
かずま さんが書きました:ポインタは配列を指す場合もありますが、
単独の変数を指す場合もあります。
後者の場合、要素は存在しません。
前者の場合、配列を指しているのだから
その配列の要素は存在します。


上記に関しては,以下のような感じでしょうか?
ポインタptr_aは単独の変数を指しているかと思います.
*ptr_aとすると,要素である10が取得出来ます.
しかし,この10という値はポインタの要素ではないということでしょうか?
コード[C++]: 全て選択
1
2
int a=10;
int * ptr_a=&a;


【補足】
かずま さんが書きました:配列の要素は連続しています。
char buff1[10], buff2[20]; のように
2つの配列があった時、それらが連続して
いるかどうかは不明です。


上記に関してですが,私の説明が悪く誤解を与えてしまったようです.
配列の要素とは,buff1[0],buff1[1]のようなものの事を指していました.
buff2,buff2+1といったポインタは連続したメモリ領域が取られると思いますが,
buff[0]とbuff[1]に関しては連続しているとは限らないという趣旨のないようでした.


配列はブロックスコープで解放されるのですね.
mallocで確保したメモリ領域はfreeしない限り解放されないということは知りませんでした.
(プログラムの終了でやっと解放される)

疑問点に関して回答頂ければ幸いです.
よろしくお願いいたします.

Name: maru
[URL]
中級者(14,574 ポイント)
Date: 2017年11月15日(水) 15:38
No: 4
(OFFLINE)

 Re: 配列とポインタの違いに関して

かずまさんではありませんが、回答してみます。

pocket さんが書きました:【疑問点1】
かずま さんが書きました:&buff1 は、配列全体(10バイトの領域)へのポインタ
&buff2 は、ポインタ buff2 へのポインタ(10バイトの領域へのポインタではない)


上記に関して,配列全体へのポインタとはint型のポインタのような感じでしょうか?
int型のポインタとは以下のようなものをイメージしています.
コード[C++]: 全て選択
1
2
int a=10;
int * ptr_a=&a;

int型のポインタは32バイトの領域のブロックを指していると思います.
同じようにbuff1はmallocで10バイトの領域のブロックが取られたものであるので,
新しく10バイトの型が作られたのだという理解でいいでしょうか?

以下のコードと実行結果をご覧ください。
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
int main(int argc, char *argv[])
{
    char    buff[10];
    char    (*p)[10] = &buff;
    printf("p = %p\n", p);
    ++p;
    printf("p = %p\n", p);
    return 0;
}
VC2015 による結果
コード[Text]: 全て選択
1
2
p = 00FCFCE8
p = 00FCFCF2

ポインタ p のインクリメントによりアドレスが10進んでいるのがわかります。つまり 10 バイト配列へのポインタ型です。

なお、int型のポインタは sizeof(int) バイトの領域のブロックで32バイトではありません。32ビットの間違いかもしれませんが...
sizeof(int) は環境依存です。

pocket さんが書きました:【疑問点2】
かずま さんが書きました:ポインタは配列を指す場合もありますが、
単独の変数を指す場合もあります。
後者の場合、要素は存在しません。
前者の場合、配列を指しているのだから
その配列の要素は存在します。


上記に関しては,以下のような感じでしょうか?
ポインタptr_aは単独の変数を指しているかと思います.
*ptr_aとすると,要素である10が取得出来ます.
しかし,この10という値はポインタの要素ではないということでしょうか?
コード[C++]: 全て選択
1
2
int a=10;
int * ptr_a=&a;

要素という用語が変数の値であるとすると、ポインタの要素はポインタが指す変数のアドレスであり、変数の要素ではありません。
ptr_a, *ptr_a, a をそれぞれ出力してみれば一目瞭然でしょう。

pocket さんが書きました:【補足】
かずま さんが書きました:配列の要素は連続しています。
char buff1[10], buff2[20]; のように
2つの配列があった時、それらが連続して
いるかどうかは不明です。


上記に関してですが,私の説明が悪く誤解を与えてしまったようです.
配列の要素とは,buff1[0],buff1[1]のようなものの事を指していました.
buff2,buff2+1といったポインタは連続したメモリ領域が取られると思いますが,
buff[0]とbuff[1]に関しては連続しているとは限らないという趣旨のないようでした.

一つの配列の要素は連続しています。連続していないとポインタ演算によって配列の要素を指定できなくなります。

Name: pocket
[URL]
初心者(8,004 ポイント)
Date: 2017年11月16日(木) 02:20
No: 5
(OFFLINE)

 Re: 配列とポインタの違いに関して

[解決!]

maruさん

非常に丁寧に回答いただきまして、本当にありがとうございます。

maru さんが書きました:なお、int型のポインタは sizeof(int) バイトの領域のブロックで32バイトではありません。32ビットの間違いかもしれませんが...


intのサイズは32ビットですね。
間違えておりました。


maru さんが書きました:要素という用語が変数の値であるとすると、ポインタの要素はポインタが指す変数のアドレスであり、変数の要素ではありません。
ptr_a, *ptr_a, a をそれぞれ出力してみれば一目瞭然でしょう。


やっとポインタというものが理解できた気がします。
ポインタとはメモリ番地とデータがセットになったものだと勘違いしておりました。
ポインタとは、単にメモリ番地を格納できる変数という役割だったのですね。

私の質問に対して、考えて下さった皆さんに感謝いたします。
本当にありがとうございました。


Return to C言語何でも質問掲示板

オンラインデータ

このフォーラムを閲覧中のユーザー: なし & ゲスト[15人]