質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

4回答

2637閲覧

C言語:非対称なポインタのポインタのメモリ確保・解放

sanshirou

総合スコア7

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

4クリップ

投稿2022/05/13 06:19

2次元の多量の要素をもつ変数を扱いたくて、配列ではなくポインタのポインタを用いてメモリを確保・解放しようとしています。
各要素はint型で、配列で書くと
p[2003][18]
に相当するのですが、次の(1)、(2)のどちらがよいのかわからなくなりました。
この処理を含むプログラム全体は大きく、実行すると途中で終了してしまいます。メモリ破壊が起こっているような気がします。
なお、
int **p, i;
とします。

C

1(1) 2// pのメモリの確保 3p = (int **)malloc(sizeof(int) * 2004); 4if (p == NULL) { 5 printf("メモリが確保できません。\n"); 6 return 1; 7} 8for (i = 0; i < 2004; i++) { 9 p[i] = (int *)malloc(sizeof(int) * 19); 10 if (p[i] == NULL) { 11 printf("メモリが確保できません。\n"); 12 return 1; 13 } 14} 15// pのメモリの解放 16for (i = 0; i < 2004 ;i++) 17 free(p[i]); 18free(p); 19 20(2) 21// pのメモリの確保 22p = (int **)malloc(sizeof(int) * 19); 23if (p == NULL) { 24 printf("メモリが確保できません。\n"); 25 return 1; 26} 27for (i = 0; i < 19; i++) { 28 p[i] = (int *)malloc(sizeof(int) * 2004); 29 if (p[i] == NULL) { 30 printf("メモリが確保できません。\n"); 31 return 1; 32 } 33} 34// pのメモリの解放 35for (i = 0; i < 19 ;i++) 36 free(p[i]); 37free(p); 38

紙に配列のようにして書いて考えてみましたが、どうしても分かりません。ご教授いただきたく、お願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

2次元の多量の要素をもつ変数を扱いたくて、配列ではなくポインタのポインタを用いてメモリを確保

ポインタ配列を使う理由は説明が無いので、誤解している可能性があると思います。

各要素はint型で、配列で書くと p[2003][18] に相当する

この配列をmalloc() で確保するとしても、私ならポインタ配列は使いません。特に必要ではなく、この大きさならなおさらです。
そこで3番目のやり方を示します。

C

1 int (*p)[18]; // p を2次元配列を指すポインタとして使う 2 int num = 1; // 配列に順に格納する値 3 4 // 2次元配列としてメモリを取得する 5 p = malloc(sizeof(int) * 2003 * 18); // キャストは不要 6 if (p == NULL) return 1; 7 8 // p[i][j] をアクセスできる。試しに順に数を格納してみる 9 for (i = 0; i < 2003; i++) { 10 for (j = 0; j < 18; j++) { 11 p[i][j] = num; 12 ++num; 13 } 14 } 15 //... 16 17 free(p); // メモリ解放は一回だけ

int (*p)[18]; は、本来的には int arr[18]; のような要素数18の配列をひとまとまりとして指すポインタですが、このポインタがあればN*18の二次元配列を指すことができ、普通の配列のようにアクセスできます。固定した 2003 * 18 というサイズが分かってるのだから、その分のメモリで十分でしょう。
二次元配列を使う時、ポインタ配列は必須ではありません。64bit環境で2003個もポインタ並べたら 8 * 2003 = 16024 byte だから16KBもメモリ使っちゃいますよ(貧乏性?w)。


3つのやり方で、それぞれどのようなメモリ配置になるのか描いてみました。
64bit機のCコンパイラは大抵 sizeof int == 4, sizeof(int*) == 8 です。この前提で取得したメモリサイスを計算しました。

(1) 2003本のポインタが並ぶ配列と、要素数18のint型配列が2003個。
(8 * 2003) + (4 * 18 * 2003) = 16024 + 144216 = 160240 bytes ≒ 160KB
イメージ説明

(2) 18本のポインタが並ぶ配列と、要素数2003のint型配列が18個。
(8 * 18) + (4 * 2003 * 18) = 144 + 144216 = 144360 bytes ≒ 144KB
上に比べればポインタ配列のサイズがぐっと小さくなったので許せる範囲かもしれません。注意深く書き換えれば動作するでしょう。しかし書き換えとは、当初 p[x][y] と書いたものを p[y][x] と書き直すことです。とてもお勧めできません。
イメージ説明

(3) 要素数 18 * 2003 のメモリを取得する。
4 * 18 * 2003 = 144216 bytes ≒ 144KB
イメージ説明

投稿2022/05/14 01:05

編集2022/05/16 06:23
rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sanshirou

2022/05/15 08:04

ご親切にご指摘ありがとうございます。私の知識のないところなので、いろいろお伺いしたいことがあるのですが、まず言葉の整理から参りたいと思います。 rubato6809さんのおっしゃる「ポインタ配列」とは、私の言う「ポインタのポインタ」、すなわち**pのことでしょうか。 それから、「3番目のやり方」とおっしゃいましたが、それに対する「1番目」、「2番目」とは、具体的に何を指すのでしょうか。 そして、私は2003×18の大きさの変数を扱おうとしていますが、どの程度ならどの方法を用いるのが適切か、その目安と言いますか、判断する方法をご教授くださいますでしょうか。 さらに、'''int (*p)[18]'''という形は初めて見ました。*を括弧で囲むのは関数へのポインタで見たことがありますが、'''int (*p)[18]'''の意味は、「要素数18の配列へのポインタ」と解釈してよろしいのでしょうか。 一度にたくさん質問して済みません。いろいろ教えていただきたいことがありますが、まずは上の質問について、一度に全部でなくてもかまいませんので、ご教授いただきたく、お願いいたします。
rubato6809

2022/05/15 11:18 編集

> 「ポインタ配列」とは「ポインタのポインタ」、すなわち**pのことでしょうか はい、概ねそういうことです。 p = malloc(sizeof(int *) * 2003); で取得するメモリはポインタの配列になります。 というか、そうするためにメモリを取得しています。 続けて for (i = 0; i < 2003; i++) { p[i] = malloc(sizeof(int) * 18); とするのだから、間違いなくそこに2003個のポインタが並びます(p[0] ~ p[2002])。 即ち p はポインタの配列をポイントします。 なんなら図を描きましょうか。気長にお待ちいただければ手書きの図をアップします。 > 「3番目のやり方」それに対する「1番目」、「2番目」とは sanshirouさんが質問に挙げた (1), (2) のどちらとも違うやり方だから、3番目です。 > int (*p)[18]; は「要素数18の配列へのポインタ」と解釈して その解釈で良いと思います。
rubato6809

2022/05/15 14:12 編集

> どの程度ならどの方法を用いるのが適切か 何GBものメモリを積んでいる今時のパソコンで数バイト程度に目くじらを立てるのもどうかと思う事はありますし、どこからもったいないと感じるか境界は人によって違うでしょうが、16KBともなれば放っておけないと思いませんか。そう感じていただきたくて「8 * 2003」を示しました。 ベストプラクティスという言葉があります。不要にできる配列を使う時点でベストプラクティスではないと思います。
sanshirou

2022/05/16 01:41

ご回答まことにありがとうございます。 私の方法とrubato6809さんの方法を並べてじっくり眺めてみました。 ``` (1)私の方法 int **p, i; // 確保 p = (int **)malloc(sizeof(int *) * 2003); if (p == NULL) return 1; for (i = 0; i < 2003; i++) { p[i] = (int *)malloc(sizeof(int) * 18); if (p[i] == NULL) return 1; } // 解放 for (i = 0; i < 2003; i++) free(p[i]); free(p); (3)rubato6809さんの方法 int (*p)[18]; int num; // 確保 p = malloc(sizeof(int) * 2003 * 18); if (p == NULL) return 1; // 解放 free(p); ``` 「違う」ことは分かりますが、具体的にメモリをどう扱うかについて、今一つ理解できないでいます。 > なんなら図を描きましょうか。気長にお待ちいただければ手書きの図をアップします。 お手数を煩わせますが、ここはご厚意に甘えて、図でご解説くださいますでしょうか。 それから、rubato6809さんの上の(3)の方法だと、使用メモリはどのように見積もっていくらになるのでしょうか。 私が最初の方法を試したのは、まず配列で(p[2003][18])プログラムを書いて(無謀ですが…)、動かなくなって、ネットで「C言語」「大きな配列」で調べて目にしたサイトの手法を取り入れたものです。malloc()関数を本格的に使うのも初めての経験です。この手法に関しては、ほぼど素人です、恥ずかしながら。 「C言語ポインタ完全制覇」という本も持って一読しましたが、私にとってはあまり身になることが書いていないように感じました。今、オライリーのポインタの本を注文してまもなく届く予定です。 独りで書いているときには、「自動変数はスタック領域を使用し、そこはできるだけ最小限の使用にとどめるため、malloc()で動的確保をするべし」という方針で、上の方法を試しました。 こういう知識とか感覚を身に付けるには、どういう勉強(おすすめの書籍とか)をすればできるようになるのでしょうか。当方、独学でC言語に関するだけでも問題集を合わせて20冊くらい所有しておりますが、いまだにこの程度です。 気長にお待ちしますので、よろしくお願い申し上げます。
fana

2022/05/17 02:34

I like this. {「int が 2003x18 個分」のひとつながりのメモリと,その先頭を指す単一のポインタ}さえあれば それで必要な物は事足りてるよね,っていう話ですね. 後は,頭の中で考えている2次元な位置に対応する場所に都度アクセスすればいいよね,っていう. で,「先頭を指すポインタ」の型( unsigned char* だろうが int* だろうがその他だろうが何でもよい)次第で位置を算出する面倒さ(?)が変わるので,そこで最も便利と思われる型を選んだ,と.
a_saitoh

2022/05/17 08:35

p[x][y]にアクセスする際に(1)、(2)は配列に2回アクセスしますが(3)だと1回なのでちょっと速い、というのもありますね。
ttb

2022/05/17 08:55

この返信者さんの「三番目」の確保方法に賛成ですね。 C言語で2次元配列を作る時は、1次元のメモリ領域を確保し、それを2次元として扱う、という思想の言語ですので。 なんとなく質問者さんの考え方 1,2 は、C#言語寄りな気がします。(C#では、たしかこのようにメモリ確保するのが原則だったはず。) > 「自動変数はスタック領域を使用し、そこはできるだけ最小限の使用にとどめるため、malloc()で動的確保をするべし」 この辺はどんな書籍がいいんでしょうねw 私は肌感覚でやっています。 開発環境VisualStudioのスタックの初期設定がたしか数万バイトくらいなのと、スタックはその場で確保して抜ける時に解放するので、数千バイトくらいにとどめるのが良い気がしています。
sanshirou

2022/05/17 12:56

rubato6809さん、遅くなりました。図解を拝見しました。ご親切にありがとうございました。図は大変明解で、違いがよく分かりました。 (2)の方法は(1)が選ばれた時点で候補から外れますので、(1)か(3)の選択のように思います。 (3)の方法でプログラムを書き直してコンパイル・実行してみますと、スッと動きました。すごいですね。 「キャストは不要」とありましたが、先に述べた「C言語ポインタ完全制覇」の本を読み直してみて、(int (*)[2003])とキャストしてみましたところ、コンパイル・実行とも問題なく進みました。「完全制覇」を前に読んだときには、まだ私の知識レベルが書籍のレベルに到達していなかったようです。 私の師匠が(もう亡くなりましたが)、「『自動』というときにも、機械任せにせずに細かく自分で指定してやれ」という方針でしたので、何となくキャストした方がいいような感覚を持っていて、キャストを試みました。 いろいろ自分でこの件についてネットで調べている途中、メモリにも「プログラム領域」、「スタック」、「ヒープ」とある事を知りましたが、そういう事も合わせて勉強したいと思っています。何かよい書籍があればお伺いしたいところです。 いろいろ勉強になりました。ありがとうございました。
sanshirou

2022/05/17 12:59

fanaさん、コメントありがとうございます。 「…そこで最も便利と思われる型を選んだ」の所が少し分かりませんでしたが、繰り返し読んで頭に入れておきます。ありがとうございました。
sanshirou

2022/05/17 13:00

a_saitohさん、コメントありがとうございました。
sanshirou

2022/05/17 13:03

ttbさん、コメントありがとうございます。 「スタックは、…数千バイトくらいにとどめるのが良い気がしています」との事、ご教授ありがとうございます。参考にさせて頂きます。
rubato6809

2022/05/18 09:25

BAを付け替えていただくとは思いませんでした。恐縮です。ありがとうございます。 ただ > (int (*)[2003])とキャスト…コンパイル・実行とも問題なく進みました が謎です。話の流れから私のコードを p = (int (*)[2003])malloc(sizeof(int)*2003*18); と変えたように読めますが、それでは話が違うので、うまくいくとは思えません。このキャストはどのコードのどこに書いたのでしょうか。
sanshirou

2022/05/19 05:53

rubato6809さん、ご返信承りました。 > このキャストはどのコードのどこに書いたのでしょうか。 おっしゃる通り、 p = (int (*)[2003])malloc(sizeof(int)*2003*18); としました。 (そのプログラムでは、BUF_SIZEを2048、LENを46として、2003の部分は(BUF_SIZE - LEN + 1)となっていますが) プログラム全体は140行程度で、この欄に書くには大きすぎると思うのですが、「どこに」とおっしゃいますと、上の通りmalloc()関数でメモリの確保を行う箇所です。 質問の最初の「2003×18の配列」に該当する変数は、やっているうちに不要であると判断し、使わなくなりました。それでも、「2048×2048」、「2003×2003」の配列(に似た変数)は必要なので、最初の質問とは違いますがこれらの変数を、ご教授いただいた配列へのポインタを使って表していました。 「Cポインタ完全制覇」の本には(今手元にないので正確ではないかもしれませんが)、「型宣言の記述から識別子を除くと型が得られる」というようなことが書いてあり、それを参考にしました。 例えば、int a;から識別子aを除いたintが型となる、という具合です(ポインタや配列、関数の場合などもっと複雑な例が挙げられていましたが、正確でないのでやめておきます。)。 問題ないと思っていましたが、違うことをやっていたのでしょうか。 プログラムの目的は、縦2048×横2048の巨大なセル集団の各セルに0~100程度の数値が格納されており、その中から、縦46×横46の領域の各セルの数値の合計を計算して、その極大値を求める、というものです。パターン認識のような概念が関連しているように思いますので、私の技量では最後まで完成させることができませんが、46×46の領域を1つの単位として、その縦・横・斜めに隣接する領域に対して極大となる領域のセル合計値とその座標をファイル出力するところまではできました。 「うまくいった」と喜んでいたのですが、よくなかったのでしょうか。 (質問欄のようにコードがきれいに表示できればコードを載せるのですが)
sanshirou

2022/05/19 05:59

すみません、上のmalloc()のメモリの確保のところは、 sizeof(int *) の間違いでした。
sanshirou

2022/05/19 06:06

再び訂正です。 上のプログラムの説明中の、「縦・横・斜めに隣接する領域」のところは、「縦・横・斜めに1つずつずれた隣り合う領域」の間違いです。隣接するわけではなく、46×46の領域同士が重なり合っている状態です。図で描ければよいのですが。
rubato6809

2022/05/19 13:41

> 「2003×18の配列」に該当する変数は、やっているうちに不要であると判断し、使わなくなりました。それでも、「2048×2048」、「2003×2003」の配列(に似た変数)は必要なので、最初の質問とは違いますが それじゃ話が合わないわけだ。
guest

0

うーん、ケチ付けて申し訳ありませんが、単純な2次元配列を作るだけなら
ポインタへのポインタではなく、単純な 2次元配列へのポインタ int (*p)[19]
をお勧めします。
また malloc 一発で領域を確保しましょう。

malloc(メモリ管理)は、処理時間をはかってみればわかりますが、信じられないくらい
重い処理です。
それを数千回呼ぶように作るのはかなりひどい悪手です。
遅いし、メモリがフラグメント化して利用効率が低下し、それがまたmallocが
遅くなる悪循環を引き起こします。

投稿2022/05/17 07:19

tknakamuri

総合スコア56

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sanshirou

2022/05/17 13:06

tknakamuriさん、ご回答ありがとうございます。 すでに先のrubato6809さんがご回答くださり、配列へのポインタの方法で無事解決しました。 malloc()関数の処理が重い処理であること、ご教授ありがとうございました。参考にさせて頂きます。
guest

0

(1)でいいですが最初のmallocは sizeof(int*) であるべきですね

投稿2022/05/13 06:28

h-okhs

総合スコア149

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sanshirou

2022/05/13 06:44

ご回答ありがとうございます。おっしゃる通り、間違えていました。 なぜ(2)ではなく(1)なのでしょうか。間違いのない考え方をご教授いただければ幸いです。
episteme

2022/05/13 06:46

(2) だと p[18][2003] になりませんか?
sanshirou

2022/05/13 07:08

そこが今一つ分からないでいます。よく考えます。ありがとうございました。
episteme

2022/05/13 07:48 編集

p[Y][X] は 「X個のカタマリ(連続した領域) を Y個」を意味します。
sanshirou

2022/05/13 08:37

> p[Y][X] は 「X個のカタマリ(連続した領域) を Y個」を意味します。 ありがとうございます。それは分かるのですが、ポインタのポインタだとどちらが先になるのか、こんがらがってしまいます。ループ処理 for (i = 0; i < 2004; i++) { p[i] = (int *)malloc(sizeof(int) * 19); のところで、「(18+1)個のかたまりが(2003+1)個」という考えでしょうか。
episteme

2022/05/13 11:27 編集

↑ですね。 "19個のカタマリをmallocする"を2004回くりかえしてますから。 ところで、a[N] の要素数はNですから、これと同義なのは malloc(sizeof(~)*N) です。N+1じゃありません。
sanshirou

2022/05/13 14:29

再三のご回答ありがとうございます。それから、N+1にする必要はなかったのですね、いろいろありがとうございました。
guest

0

C

1// pのメモリの確保 2p = (int **)malloc(sizeof(int) * 2004); 3 4// pのメモリの確保 5p = (int **)malloc(sizeof(int) * 19);

ここで確保すべきなのはポインタなので、sizeof(int)でなくsizeof(int*)でなければなりません(ポインタのほうが大きい場合、メモリが不足します)。

投稿2022/05/13 06:25

maisumakun

総合スコア145121

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sanshirou

2022/05/13 06:45

ご回答ありがとうございます。おっしゃる通り、間違えていました。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問