
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をこのまま出力すると変?な画像となって出力されてしまいました。
上のように記述した場合、矛盾する場所はあるでしょうか?
そして、この方法以外に配列を確保する方法はあるでしょうか?
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() などは『実際のメモリ使用量=メモリ管理情報(領域)+確保サイズ』です。
返答ありがとうございました。
丁寧に説明いただきありがとうございます。
いろいろと質問してしまいましたが、やっと理解できました。
mallocは『実際のメモリ使用量=メモリ管理情報(領域)+確保サイズ』つまりmallocを行わなければ、いくらポインタをmallocで確保したとしても管理領域は発生しないということですね。
自分が質問に書いた”ysize分のポインタを確保”これはポインタを確保しただけであり、この時点では管理領域は発生しておらず、その後自分が書いたソースのように記述すると、各行ごとに管理領域が発生してしまうが、Oh-Orangeさんが提示してくださったプログラムではそれが発生しない。
自分の勘違いの基は「ポインタ変数=管理領域」という思い込みに原因がありましたねorz
このような理解でよいでしょうか?
そして最後にもう1つ気がかりが・・・・
この質問で初めに答えてくださったプログラムについてです。
そのプログラムではbuffのアドレスをsrcへ格納しています。
もしその後free(src)を行った場合、buffへの影響はないでしょうか?
No.5
- 回答日時:
どうしても使うメモリを xsize * ysize (くらい: malloc の管理領域はどうしても必要だから) にするのであれば, src を unsigned char * で定義してマクロで逃げる.
つまり
#define src(i, j) src[(i)+xsize*(j)]
というマクロを用意してメモリは
src = malloc(xsize * ysize);
のように確保する. そうすれば size(x, y) という形でアクセスできる, はず.
姑息だけど.
返答遅れて申し訳ありませんでした。
確かにマクロで #define src(i, j) src[(i)+xsize*(j)] このように記述すれば、2次元配列として扱うことができますね。
アドバイスをしていただき、ありがとうございました。
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 関数などのメモリ確保では、数バイトの管理領域+確保容量の合計が実際のメモリ上に
確保されるサイズ(バイト)です。このような内部的な仕組みを考えていないと確保容量を本当に
節約するプログラムは記述できません。→知っていればの話ですけど。
・以上。補足情報でした。
返答が遅れてしまい、申し訳ありませんでした。
ちょっと込み入ってて・・・
確かに自分の方法では管理領域としてysize分を必要とする記述方法になりますね。
Oh-Orangeさんが記述された
src = (unsigned char **)malloc( sizeof(unsigned char *) * ysize );
このプログラムもysize分のポインタを確保して、そのポインタの先頭アドレスをsrcに代入するという意味では 管理領域としてysize分を必要とする記述になり、自分が書いたプログラムと同じ管理領域にならないのでしょうか?
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 以外のメモリ確保関数でも同じです。
・以上。参考に!
返答ありがとうございました。
自分もこの方法を考えましたが、確かにうまく格納できると思います。しかしメモリ確保の量を考えると、sizeof(unsigned char) * xsize * ysize + sizeof(unsigned char *) * ysizeになると思います。
できるだけxsize*ysizeにしようと思っていたのですがちょっと難しそうです。
しかしこの方法を用いれば確実に2次元配列で扱うことができるし、画像の保存時もbuffをfwrite関数の引数に与えればいいだけなので、ある意味一番効率が良い方法かもしれません。
アドバイスありがとうございました。
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);
なのでは?
src = (unsigned char *)malloc(xsize * ysize);
fp = fopen(filename, "rb");
fread(src[0], xsize , ysize, fp);
確かにこのプログラムなら正常に読み込めると思います。
しかし・・・私が説明不足でしたね。
わざわざ**srcを使用した理由は通常の2次元配列同様に亜通ことができるからです。 src[][]のように。
この画像を読んでから複雑な処理を行うため、二次元配列同様に扱えたらとても理解しやすくなるので、**srcを用いました。
アドバイスありがとうございました^^
No.1
- 回答日時:
> srcをこのまま出力すると
ここのコードを省略せずに書いてほしかったです。
ヘッダーファイルのインクルードや、関数の定義など、
どうせなら書かれたコードを全部出してみませんか?
返答ありがとうございました。
と、同時に申し訳ありませんでした。
確かにこの疑問がsrcを表示させる部分に間違いがある可能性も十分にあるため、全てのコードを乗せるのが定石だとは思ったのですが、自分が不安を持っている部分について主に質問したかったためこのように記述してしまいました。
申し訳ないです。
やはり全てのコードを乗せたほうが良いですよね。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# このプログラミング誰か教えてくれませんか 1 2022/06/02 15:27
- C言語・C++・C# c言語配列の結合についてです。 なぜうまくいかないのでしょうか。 #include <stdio.h 4 2022/05/30 22:42
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- C言語・C++・C# 宣言する関数の形が決まっている状態で、 str1とstr2の文字列をこの順に引っ付けてstrに保存し 2 2022/05/30 18:21
- C言語・C++・C# C言語 プログラミング 4 2022/05/22 11:53
- C言語・C++・C# c言語でユーザ関数を利用して入力された文字列を反転させるプログラムを作りたいです。 3 2023/01/29 19:47
- C言語・C++・C# プログラミングの授業の課題です 1 2023/01/17 22:15
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# const char** p;のとき、free(p)でC4090エラーとなるのはなぜですか 3 2023/03/31 16:28
- C言語・C++・C# C言語初心者 構造体 課題について 2 2023/03/10 19:48
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
allocってなんですか?
-
行列内の行の交換,列の交換を...
-
C++のnewで確保したメモリーの...
-
malloc呼び出し時のセグメンテ...
-
構造体でchar name[]と*nameの...
-
ヒープメモリの解放について
-
LoadLibraryでAccess Violation...
-
指定したメモリアドレスの値の...
-
メモリ不足になってしまう。
-
配列の添え字の最大数とは?
-
スタック破壊の上手な見つけ方...
-
文字列のメモリ保持期間とポイ...
-
CreateFileMapping について
-
C言語 配列の長さの上限
-
init関数の意味
-
ポインターの使用法や利点
-
配列の要素数に変数を入れたい...
-
セグメントエラー
-
getchar,isalphaについて
-
CStringからchar*への型変換に...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
c言語のポインタへの文字列入力...
-
allocってなんですか?
-
newしないオブジェクトについて
-
malloc呼び出し時のセグメンテ...
-
入れ子になった構造体について
-
ヒープメモリの解放について
-
ビットをローテートするプログ...
-
C++で、メンバもヒープに確保さ...
-
void*型のデータサイズ
-
Win32APIでのメモリ管理について
-
配列の添え字の最大数とは?
-
C++のnewで確保したメモリーの...
-
プログラムが途中で強制終了し...
-
C言語 mallocとfreeについて
-
win32APIのHeapAlloc()の使い方...
-
LoadLibraryでAccess Violation...
-
グローバル変数のサイズ
-
MFCのCStringについて
-
メモリ不足になってしまう。
-
ヒープの実際の限界値は?
おすすめ情報