• ベストアンサー

画像を読み込む配列の確保。

C言語について質問です。 画像を読み込む時、その画像の幅、高さを入力し、そのサイズに見合った配列を確保します。その後ファイル名を入力し、配列に読み込むプログラムを作成しました。 メインの部分のみ記述します。 int xsize, ysize, i; unsigned char **src; char filename[30]; FILE *fp; printf("ファイル名を入力してください:"); scanf("%s", filename); printf("画像の幅:");scanf("%d", &xsize); printf("画像の高さ:");scanf("%d", &ysize); src = (unsigned char **)malloc(sizeof(unsigned char *) * ysize); for(i=0; i<ysize; i++) src[i] = (unsigned char *)malloc(sizeof(unsigned char) * xsize); fp = fopen(filename, "rb"); fread(src[0], sizeof(unsigned char), xsize * ysize, fp); このように記述し、エラーもなく実行できたのですが、srcをこのまま出力すると変?な画像となって出力されてしまいました。 上のように記述した場合、矛盾する場所はあるでしょうか? そして、この方法以外に配列を確保する方法はあるでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.6

★最初に ysize 分のポインタを管理領域と呼んでいません。 ・プログラマが malloc(1000) として 1000 バイトを確保した場合、  実際のメモリ上には  (1)malloc() 関数がメモリ確保の管理情報を数バイト保持(8~16バイトほど)  (2)実際にプログラマが使用できる 1000 バイトの領域  となります。つまり、使用できる 1000 バイトの領域の先頭に数バイトのメモリ管理情報が  存在するのです。 ・詳しい内部の仕組みは分かりませんが、確保したメモリ領域をリストデータで管理していると  思います。リストデータはポインタで次々にデータをチェインしますよね。  そのとき、次のデータ位置を表すポインタや確保サイズなどの数バイトが malloc() 関数などの  メモリ管理情報(領域)として存在するわけです。 ・以上を踏まえて回答 No.4 を読んでみて下さい。  もう少し分かりやすく説明すると  char *a = malloc(1000);  char *b = malloc(2000);  char *c = malloc(3000);  char *d = malloc(4000);  の4つのメモリは合計で 10000 バイト確保され 10000 バイトを使用できます。  でも、実際のメモリ上には、メモリ管理情報(領域)=ヘッダが存在しますので  a…8 + 1000  b…8 + 2000  c…8 + 3000  d…8 + 4000  で合計 10032 バイトのメモリを消費します。なお、8 バイトが管理情報ですが、昔使っていた  コンパイラを調べたときのサイズです。今はどれくらいか分かりませんが数バイトはあります。 ・メモリを節約したい場合は、回答者 No.5 さんのマクロ関数が一番節約できると思います。  前回も同じような感じでアドバイスしたときにマクロ関数を紹介しようと思いましたが、  それを使うのがスッキリしなくて unsigned char **src を使って src[y][x] とアクセスしたいのかと  思いましたのでマクロ使用の紹介は控えました。が、他の回答者さんのお礼にマクロ関数のことが  よく分からないとなっていましたね。今回はマクロ関数を利用する方がメモリを節約できます。 ・マクロ関数を利用すれば、画像の横×高さのサイズ分だけ確保すればよいので src[] のポインタ分の  メモリは節約できます。 ・以上。malloc() などは『実際のメモリ使用量=メモリ管理情報(領域)+確保サイズ』です。

noconan
質問者

お礼

返答ありがとうございました。 丁寧に説明いただきありがとうございます。 いろいろと質問してしまいましたが、やっと理解できました。 mallocは『実際のメモリ使用量=メモリ管理情報(領域)+確保サイズ』つまりmallocを行わなければ、いくらポインタをmallocで確保したとしても管理領域は発生しないということですね。 自分が質問に書いた”ysize分のポインタを確保”これはポインタを確保しただけであり、この時点では管理領域は発生しておらず、その後自分が書いたソースのように記述すると、各行ごとに管理領域が発生してしまうが、Oh-Orangeさんが提示してくださったプログラムではそれが発生しない。 自分の勘違いの基は「ポインタ変数=管理領域」という思い込みに原因がありましたねorz このような理解でよいでしょうか? そして最後にもう1つ気がかりが・・・・ この質問で初めに答えてくださったプログラムについてです。 そのプログラムではbuffのアドレスをsrcへ格納しています。 もしその後free(src)を行った場合、buffへの影響はないでしょうか?

その他の回答 (5)

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

どうしても使うメモリを xsize * ysize (くらい: malloc の管理領域はどうしても必要だから) にするのであれば, src を unsigned char * で定義してマクロで逃げる. つまり #define src(i, j) src[(i)+xsize*(j)] というマクロを用意してメモリは src = malloc(xsize * ysize); のように確保する. そうすれば size(x, y) という形でアクセスできる, はず. 姑息だけど.

noconan
質問者

お礼

返答遅れて申し訳ありませんでした。 確かにマクロで  #define src(i, j) src[(i)+xsize*(j)]  このように記述すれば、2次元配列として扱うことができますね。  アドバイスをしていただき、ありがとうございました。

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.4

★メモリ確保量を考えるのならば、一度に大きい配列を確保した方が良い。 >src = (unsigned char **)malloc(sizeof(unsigned char *) * ysize); >for(i=0; i<ysize; i++) > src[i] = (unsigned char *)malloc(sizeof(unsigned char) * xsize);  ↑の方法では、ysize 分のメモリ管理領域(数バイト)のサイズ分だけ無駄になります。  つまり、メモリ管理領域を 8 バイトと仮定して、ysize=1000px とすると  8バイト×1000px=8000バイト分の管理領域が必要になります。  でも、 >buff = (unsigned char *)malloc( sizeof(unsigned char) * xsize * ysize ); >src = (unsigned char **)malloc( sizeof(unsigned char *) * ysize );  ↑の方法ならメモリ管理領域は2つ分で済みます。→8バイト×2個=16バイト  8000バイトと16バイトでは、どちらがメモリ量を節約できるか分かりますよね。 ・malloc 関数などのメモリ確保では、数バイトの管理領域+確保容量の合計が実際のメモリ上に  確保されるサイズ(バイト)です。このような内部的な仕組みを考えていないと確保容量を本当に  節約するプログラムは記述できません。→知っていればの話ですけど。 ・以上。補足情報でした。

noconan
質問者

お礼

返答が遅れてしまい、申し訳ありませんでした。 ちょっと込み入ってて・・・ 確かに自分の方法では管理領域としてysize分を必要とする記述方法になりますね。 Oh-Orangeさんが記述された src = (unsigned char **)malloc( sizeof(unsigned char *) * ysize ); このプログラムもysize分のポインタを確保して、そのポインタの先頭アドレスをsrcに代入するという意味では 管理領域としてysize分を必要とする記述になり、自分が書いたプログラムと同じ管理領域にならないのでしょうか?

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.3

★アドバイス ・src[0] のメモリ領域は xsize 分しか確保されていません。 >fread(src[0], sizeof(unsigned char), xsize * ysize, fp);  とするとメモリ領域が足りないため、正しく読み込めませんよ。  src[0]、src[1]、src[2]…src[ysize - 1]のメモリ領域は連続して確保されることは  ないと思います。通常、malloc() 関数ではメモリ管理の為に数バイトのヘッダ情報が  あるためメモリ領域が連続して確保されません。無理! 解決法: ・一度に大きい配列領域を確保してから以前に紹介した FuncSetArray() 関数のようなやり方で  unsigned char **src のポインタにセットして下さい。 ・下にサンプルを載せます。 サンプル: int xsize, ysize, i; unsigned char **src; unsigned char *buff; FILE *fp; // メモリの確保 buff = (unsigned char *)malloc( sizeof(unsigned char) * xsize * ysize ); src = (unsigned char **)malloc( sizeof(unsigned char *) * ysize ); // ファイルの読み込み fp = fopen( filename, "rb" ); fread( buff, sizeof(unsigned char), (xsize * ysize), fp ); fclose( fp ); // 縦軸の配列セット for ( i = 0 ; i < ysize; i++ ){  src[ i ] = &buff[ xsize * i ]; } : 処理 : その他: ・上記のサンプルではエラーチェックなしです。チェックして下さい。  連続したメモリ領域 buff を確保して、その領域に fread で読み込めばデータが必ず  連続して読み込まれます。ラインごとに分割してメモリを確保した場合は、すべての  データが連続しません。malloc 以外のメモリ確保関数でも同じです。 ・以上。参考に!

noconan
質問者

お礼

返答ありがとうございました。 自分もこの方法を考えましたが、確かにうまく格納できると思います。しかしメモリ確保の量を考えると、sizeof(unsigned char) * xsize * ysize + sizeof(unsigned char *) * ysizeになると思います。 できるだけxsize*ysizeにしようと思っていたのですがちょっと難しそうです。 しかしこの方法を用いれば確実に2次元配列で扱うことができるし、画像の保存時もbuffをfwrite関数の引数に与えればいいだけなので、ある意味一番効率が良い方法かもしれません。 アドバイスありがとうございました。

  • buriburi3
  • ベストアンサー率44% (353/792)
回答No.2

srcが連続領域である保障がありません。 fread(src[0], sizeof(unsigned char), xsize * ysize, fp); が何処に読み込まれるか不確実です。 fp = fopen(filename, "rb"); for(i=0; i<ysize; i++) {  src[i] = (unsigned char *)malloc(sizeof(unsigned char) * xsize);  fread(src[i], sizeof(unsigned char), xsize , fp); または src = (unsigned char *)malloc(xsize * ysize); fp = fopen(filename, "rb"); fread(src[0], xsize , ysize, fp); なのでは?

noconan
質問者

お礼

src = (unsigned char *)malloc(xsize * ysize); fp = fopen(filename, "rb"); fread(src[0], xsize , ysize, fp); 確かにこのプログラムなら正常に読み込めると思います。 しかし・・・私が説明不足でしたね。 わざわざ**srcを使用した理由は通常の2次元配列同様に亜通ことができるからです。 src[][]のように。 この画像を読んでから複雑な処理を行うため、二次元配列同様に扱えたらとても理解しやすくなるので、**srcを用いました。 アドバイスありがとうございました^^

  • asuncion
  • ベストアンサー率33% (2127/6289)
回答No.1

> srcをこのまま出力すると ここのコードを省略せずに書いてほしかったです。 ヘッダーファイルのインクルードや、関数の定義など、 どうせなら書かれたコードを全部出してみませんか?

noconan
質問者

お礼

返答ありがとうございました。 と、同時に申し訳ありませんでした。 確かにこの疑問がsrcを表示させる部分に間違いがある可能性も十分にあるため、全てのコードを乗せるのが定石だとは思ったのですが、自分が不安を持っている部分について主に質問したかったためこのように記述してしまいました。 申し訳ないです。 やはり全てのコードを乗せたほうが良いですよね。

関連するQ&A