4GBを越えるファイルサイズの扱い方に関して

[戻る]
2005/02/16(水) 19:42:07
こんにちは
4GBを越えるファイルのサイズの扱い方で質問です

環境
WinXP、VC++6.0、MFC、SDI

SHGetDataFromIDList()やFindFirstFileによって得られたWIN32_FIND_DATA構造体の中の
nFileSizeHighとnFileSizeLowの使い方ですが

例えば
nFileSizeHigh == 0x0000 0050
nFileSizeLow  == 0x0010 0000
という数値が得られたとき、実際のファイルサイズは64bitの整数値で考えると
0x0000 0050 0010 0000
となるという考え方で間違いないでしょうか

以下に示すコードのdwHighに適当な数値を(勝手に)入れて実験すると
ちゃんと予想した数値が表示されるので問題はなさそうなのですが、
実際にdwHighに数値が入るようなサイズの大きいファイルを持っていないために
きちんとした実験ができず不安なのです

また、これを文字列に変換する必要があり、現在以下のようなコードを書いています
WFDは取得されたデータが入ったWIN32_FIND_DATA構造体の変数です
それとlpszFileSizeは最終的に文字列が格納されるchar型配列へのポインタです

DWORD    dwHigh    = WFD.nFileSizeHigh;
DWORD    dwLow    = WFD.nFileSizeLow;
__int64    nSize;
int    nLen;
char    szTemp[MAX_PATH];
char*    p;
int    nCnt;

nSize    = dwHigh;            //上位32bit
nSize    = (nSize << 32) + 1023;    //1023は1KB未満を切り上げるため
nSize    += dwLow;            //下位32bit
nSize    = (nSize >> 10);        //KB単位にする

_i64toa(nSize,szTemp,10);        //文字列に変換

nLen    = lstrlen(szTemp);
p    = lpszFileSize;

//3桁ごとにカンマを入れる
for(nCnt=0;nCnt<nLen;nCnt++)
{
    *p++    = szTemp[nCnt];
    if((nCnt != nLen - 1) && ((nLen-(nCnt+1))%3 == 0))
        *p++    = ',';
}
*p    = '\0';

lstrcat(p," KB");

ここで質問したいのは、__int64に関してです
この型はMicrosoft固有の仕様だということでC++の仕様外のもののようなのですが
C++の仕様に沿った形でDWORD2つで表される64bit整数値を
文字列に変換することはできないでしょうか

もっとも、実際にこのプログラムを他の環境に移すことはまず無いと思うので
このままでも問題はないかもしれません
しかしもし方法があるなら知識として知っておきたいのです

また、ここはおかしいよ、とかここはもっとシンプルにできるよなど
アドバイスがあればいただきたく思います

以上が質問内容です
よろしくお願いします

シャノン 2005/02/16(水) 19:53:43
> となるという考え方で間違いないでしょうか

はい。

> 実際にdwHighに数値が入るようなサイズの大きいファイルを持っていないために
> きちんとした実験ができず不安なのです

作っちゃえ。
スパースファイルを使うと、簡単に作れます。

> C++の仕様に沿った形でDWORD2つで表される64bit整数値を
> 文字列に変換することはできないでしょうか

C++ の仕様では、そもそも DWORD 2つで 64bit であることさえ規定されてないので、
無理じゃないかと思われます。

KB 単位にしたいならば、StrFormatKBSize という API で一発、とか。

tib 2005/02/16(水) 20:00:36
> nFileSizeHigh == 0x0000 0050
> nFileSizeLow  == 0x0010 0000
> という数値が得られたとき、実際のファイルサイズは64bitの整数値で考えると
> 0x0000 0050 0010 0000
> となるという考え方で間違いないでしょうか

MSDNを見たら、

The size of the file is equal to (nFileSizeHigh * MAXDWORD) + nFileSizeLow. 

と書いてました。というわけで、

nLen = sprintf( szTemp, "%I64d", (dwHigh* MAXDWORD) + dwLow );

ではどうでしょうか。

tib 2005/02/16(水) 20:18:02
ん? castが必要かな?

nLen = sprintf( szTemp, "%I64d", ((__int64)dwHigh* MAXDWORD) + dwLow );

余談ですが、これでフォーマットを「%016I64x」にすると、
「00000050000fffb0」が得られます。

2005/02/16(水) 23:38:19
こんばんわ
シャノンさん、tibさん、レスありがとうございます

> 作っちゃえ。
> スパースファイルを使うと、簡単に作れます。

作っちゃいました
ほんとに簡単に作れるものなんですね
なお、コレを使った実験結果は後述します

> C++ の仕様では、そもそも DWORD 2つで 64bit であることさえ
> 規定されてないので、無理じゃないかと思われます。

そうですか・・・
LONGLONGなんかも見てみましたけど、あれも結局は
typedef __int64 LONGLONG
とかなってましたし
__int64ではなくdoubleの場合もあるみたいですけど、
どういう場合分けがされているのかはよくわかりませんでした

> KB 単位にしたいならば、StrFormatKBSize という API で一発、とか。
> sprintf( szTemp, "%I64d", ((__int64)dwHigh* MAXDWORD) + dwLow );

これらは両方ともうまく変換できました
StrFormatKBSize()はカンマと単位(KB)を自動でつけてくれる優れものですね
ただ強制的についてしまうので場合によってはsprintf()も応用できそうです
それにしてもsprintf()の書式でI64なんてものがあったのを初めて知りましたよ・・・

ちなみに

> MSDNを見たら、
> The size of the file is equal to (nFileSizeHigh * MAXDWORD) + nFileSizeLow. 
> と書いてました。

これは思いっきり見逃してました・・・
何回かヘルプの説明に目を通したつもりが・・・
わたしの目はどこを見てたんでしょ・・・

で、これにて一件落着かと思ったんですが、そううまくもいってくれませんでした
もしよろしければもう少しお付き合いいただけるとありがたいです

新しく出てきた問題点なのですが

SHGetDataFromIDList(lpShellFolder,lpItemIDL,SHGDFIL_FINDDATA,
                &WFD,sizeof(WIN32_FIND_DATA));

このようにして先ほどの10GBのファイルのItemIDListからWIN32_FIND_DATAを
取得してみました

しかし、ここでなぜか以下のような数値が得られました
WFD.nFileSizeHigh == 0
WFD.nFileSizeLow == 0xffffffff
関数の戻り値はNOERRORなので正常終了しているはずです

ここで試しにGetFileSize()およびFindFirstFile()で実験してみたところ、
WFD.nFileSizeHigh == 0x00000002
WFD.nFileSizeLow == 0x80000000
という結果が得られました
これが正しい数値ですよね

SHGetDataFromIDList()では4GBを越えるファイルサイズは取得できないんでしょうか?
何か知っている方がいましたらアドバイスお願いします

2005/02/17(木) 00:26:06
追加情報です

さきほど10GBのファイルを作ったのと同じ方法でいくつか大容量の
スパースファイルを作成して試してみたところ、

MAXDWORDにあたる0xffffffffまでのファイルサイズの数値は正常に取得できるようです
が、
ファイルサイズがそれ以上になった場合、
上位DWORDが0、下位DWORDは0xffffffffとなるようです

試したのは
4GB-2Byte
4GB-1Byte   // == MAXDWORD
4GB         // == MAXDWORD + 1
4GB+1Byte
4GB+2Byte
8GB
10GB

これらで試した結果、
4GBを境に上記のように上位が0、下位が0xffffffffとなってしまいます
今のところわかっているのはそのくらいです

また何かわかったら追記します

2005/02/17(木) 00:37:46
改めてMSDN(英語)を見てみたら

http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/shellcc/platform/shell/reference/functions/shgetdatafromidlist.asp

ここに

> if you set nFormat to SHGDFIL_FINDDATA,
> the function might assign meaningful values to
> only some of the members of the WIN32_FIND_DATA structure.
>  The remaining members will be set to zero.
> To retrieve complete current information on a file system file or folder, 
> use standard file system functions such as GetFileTime or FindFirstFile.

と書いてありました

これが原因だったのかもしれませんね・・・
具体的にどれが0になる可能性があるかとは書いてないので
詳しいことはなんともいえませんが・・・

ここは下位DWORDが0xffffffffだった場合はFindFirstFileなどで取得しなおすか、
最初からそうするかに方法を切り替えたほうがよさそうです

もっとちゃんとMSDNを読むべきでしたね
無駄に長いレスをつけてしまって申し訳ありませんでした

最後になりましたがシャノンさん、tibさん、
質問に答えていただきありがとうございました
ではこれで失礼します

2005/02/17(木) 00:39:13
[[解決]]
はっ・・・申し訳ありません
解決チェック付け忘れました・・・

(T-T)

失礼しました

シャノン 2005/02/17(木) 09:59:02
>> C++ の仕様では、そもそも DWORD 2つで 64bit であることさえ
>> 規定されてないので、無理じゃないかと思われます。

> そうですか・・・
> LONGLONGなんかも見てみましたけど、あれも結局は
> typedef __int64 LONGLONG
> とかなってましたし
> __int64ではなくdoubleの場合もあるみたいですけど、
> どういう場合分けがされているのかはよくわかりませんでした

C++ の仕様では、

・1バイトは最低8ビットはなければならない(8ビット以上あってもいい)
・char のサイズは1バイトでなければならない
・int は、対象マシンの CPU で最も効率的に処理できるサイズ
・short int は、int 以下のサイズ
・long int は、int 以上のサイズ
・long long int は、long 以上のサイズ

というような(正確ではないかも)決め方をしているので、sizeof( int ) == sizeof( 
short ) == sizeof( long ) == 20 というコンパイラがあっても、規格違反ではありま
せん。
__int64 は Microsoft の拡張なので、ピッタリ 64bit ですが、C++ に 64bit ピッタ
リ、またはそれ以上のサイズを持っていると保障される型はありません(char[8] とか 
malloc とかは例外とする)。

double も float 以上の精度を持っているという規定しかないので、double が 64bit 
である保証はありません。
ただし、float と double は IEEE の規格に基づく処理系が多いようなので、IEEE の浮
動小数点規格に準拠していれば、32bit / 64bit であると言えるでしょう。
もちろん、C++ の規格では、IEEE の規格に準拠していなければならないとは決まってい
ません。

ついでに、printf の I64 も Microsoft 拡張です。
極論、「サイズ」という概念は、CPU やメモリ、ディスクなどの環境に左右されるた
め、C++ は言語規格では極力そういうものを既定しない方針と言えます。

2005/02/17(木) 11:13:47
シャノンさん、わかりやすい解説ありがとうございます

> ・1バイトは最低8ビットはなければならない(8ビット以上あってもいい)
> ・char のサイズは1バイトでなければならない
> ・int は、対象マシンの CPU で最も効率的に処理できるサイズ
> ・short int は、int 以下のサイズ
> ・long int は、int 以上のサイズ
> ・long long int は、long 以上のサイズ

コレに関しては大雑把な理解ですが、知っていました
なので、というわけではないかもしれませんが
__int64はintの倍の領域を持つ(intのサイズに左右される)のかなと
漠然と考えてしまっていました

しかし型名に64という数字がついているのに、
状況によってサイズが変わってしまってはややこしくなってしまいますよね

ここで少し疑問に思ったのですが、__int8、__int16、__int32に関してヘルプでは

> __int8 データ型は char 型に相当し、
> __int16 は short 型、
> __int32 は int 型に相当します

とあります
ファイル検索をかけてみましたがこれらの型の定義は見つかりませんでした
すでに組み込まれているようですが、これらの型は__int64と違って
環境によって変わる可能性があるということなんでしょうか

それとも
「それぞれのサイズはぴったり8、16、32bitだが、
VCにおける型のサイズはchar、short、intと同じである」
という意味(わかりにくいかも・・・)なんでしょうかね

でもそのすぐ下に

> コンパイラは __int8、__int16、__int32 をすべて同義語として扱うので、
> これらの型をオーバーロード関数呼び出しの引数として使うときは
> 注意してください。

と書いてありますから、やっぱり完全に同一のものと見るべきなんでしょうか・・・

もうなんかわけがわからなくなってきました

そもそもこれらの型はMicrosoft固有の仕様と書いてあるので、
VC以外では使用できない、つまりこれらの変数が使用できる環境において
これらのサイズは必ず8、16、32であるということになるのでしょうか

実際に他の開発ツールを使用したことが無いのでわからないのですが、
他の開発環境ではまたそれら固有の64bit変数が用意されてたりするんでしょうか?

それともこれはC++準拠ではないが、Microsoft Windowsに対する仕様であり、
Windowsプログラミングのための開発ツールには入っていないといけないとか?
「Microsoft固有の」という表現がいまいち何を指すのかはっきりしません

ファイルサイズに関して調べているときに
「ファイルサイズは64bitで管理されている」
という記述をどこかで見たことがあるのですが、この文章が正確なものなら
64bit変数というのはWindowsプログラミングには必要なもののように思えてきますし

書いてるときにふと思ったのですが、
前のレスで書いたように「LONGLONGがdoubleで定義されている場所もあった」
となっていたのはつまり__int64が定義されていないような環境では
doubleを使うという意味で、すなわち__int64が共通の仕様ではないということでしょう
か
完全に予測の範囲を出ませんが

何か疑問が疑問を呼んで質問攻めみたいになってしまってすみません
しかも長文乱文・・・

簡単に解説していただけるとありがたいですが、
もし「ここをよく読むとわかるかも」というようなサイトなどありましたら
紹介していただけるだけでもうれしいです

シャノン 2005/02/17(木) 11:38:50
> 「それぞれのサイズはぴったり8、16、32bitだが、
> VCにおける型のサイズはchar、short、intと同じである」
> という意味(わかりにくいかも・・・)なんでしょうかね

VC++ の実装がどうなっているかわかりませんが、推測でよければ…
それぞれのサイズはぴったり 8、16、32bit である。
VC++ においては、char、short、int も、たまたまぴったり 8、16、32bit である。
だから「相当する」ということなのではないかな、と。
将来の VC++ で int が 64bit になったとしても(IA-64 用 コンパイラでは 64bit だ
ったり…しないよね?)、__int32 は 32bit ではないでしょうか。
その時は、__int32 は int に相当しなくなるだけです。

> でもそのすぐ下に

>> コンパイラは __int8、__int16、__int32 をすべて同義語として扱うので、
>> これらの型をオーバーロード関数呼び出しの引数として使うときは
>> 注意してください。

> と書いてありますから、やっぱり完全に同一のものと見るべきなんでしょうか・・・

単にオーバーロードの対象にならないだけで、別物と考えています。

> 実際に他の開発ツールを使用したことが無いのでわからないのですが、
> 他の開発環境ではまたそれら固有の64bit変数が用意されてたりするんでしょうか?

されている可能性はありますし、VC++ がデファクトスタンダードなので、それに倣っ
て __int64 などを実装している処理系がある可能性もあります。

> 64bit変数というのはWindowsプログラミングには必要なもののように思えてきますし

64bit 変数は必要でしょうが、組み込み 64bit 型は必ずしも必要では無いでしょう。あ
れば便利だと言うだけのことだと思います。

> __int64が定義されていないような環境では
> doubleを使うという意味で、すなわち__int64が共通の仕様ではないということでしょ
うか

たまたま 64bit だから、流用したというだけの事でしょう。
例えば、こんなの

double d;
*( ( LARGE_INTEGER * )&d ) = 0x0123 4567 89AB CDEF;
とかやったら、もはやこれは意味のある double 値ではないですから
(見易さのために4桁区切りにしてあります)。

> 何か疑問が疑問を呼んで質問攻めみたいになってしまってすみません

俺のも全部推測に過ぎません。
ただ、ひとつ言うならば「そんなに気にしなくてもいいんじゃない?」とは思います。

移植性を考えた話ならば、ファイルサイズを 64bit で表さない環境もあるでしょうか
ら、そういう環境に依存しない部分のコードしか、クロスプラットフォーム性は保てな
いでしょう。

シャノン 2005/02/17(木) 11:39:56
> double d;
> *( ( LARGE_INTEGER * )&d ) = 0x0123 4567 89AB CDEF;

ごめんコンパイルエラーだ。

2005/02/17(木) 12:26:30
何度もお返事ありがとうございます

> 俺のも全部推測に過ぎません。
> ただ、ひとつ言うならば
> 「そんなに気にしなくてもいいんじゃない?」とは思います。

確かにそうなんですけどね
実際気にしたところで別に移植性を考えなければいけないような
プログラムを作っているわけでもありませんし

ただ、今は必要の無い知識だとしても、知っていることによって
あとあと活きてくる知識というのは多いと思っています
そのために、もしわかるのであれば知りたかったという感じですね

レスの内容はあくまでも予測の範囲を出ないとのことですが、
参考にさせていただきます

> 単にオーバーロードの対象にならないだけで、別物と考えています。

そういえばオーバーロードは型が同一かどうかではなくて、
呼び出すときにどのタイプを呼び出すかを判別できるかどうかでしたっけ
そう考えると確かに型が同一と考える材料にはならないですね
ちゃんと理解してないのがバレてしまう・・・

> double d;
> *( ( LARGE_INTEGER * )&d ) = 0x0123 4567 89AB CDEF;
> ごめんコンパイルエラーだ。

double d;
((LARGE_INTEGER*)&d)->QuadPart = 0x0123456789ABCDEF;
printf("%I64x\n",d);

としたら
0123456789abcdef
と出力されました
こんなことをして意味があるかは別としてですが・・・

また、このLARGE_INTEGERというものを調べてみたところ、共用体なんですね

LARGE_INTEGER    size;
size.LowPart    = WFD.nFileSizeLow;
size.HighPart    = WFD.nFileSizeHigh;
StrFormatKBSize(size.QuadPart,lpszFileSize,MAX_PATH);

こんな感じにできてソースがわかりやすくなるかもしれません
ちょっと採用を考えてみます

本題から逸れてしまったにも関わらずお付き合いいただきありがとうございました

補足厨 2005/02/17(木) 13:03:08
> VC++ がデファクトスタンダードなので、

Windows における、デファクトスタンダード.


> それに倣って __int64 などを実装している処理系がある可能性もあります。

GNU g++ におけるの独自拡張、long long int は 64bit.
C99 の long long int とほぼ互換だが、VC の __int64 とは関係ない.

C99 規格の stdint.h が参考になる.
将来の C++ では cstdint だろうか.

2005/02/17(木) 21:05:42
補足厨さん、補足ありがとうございます

C99の規格を解説しているサイトもあるようですから、
そういったところを読んでみようと思います

今までは規格とかは全然勉強したこと無かったんですが、
知っておいたほうがいいことは多いでしょうし

以上、どうもありがとうございました

シャノン 2005/02/17(木) 22:42:31
ちなみに、C と C++ の最新規格の日本語版が、日本工業標準調査会
(http://www.jisc.go.jp/)のページで、全文タダで閲覧できます。
「JIS 検索」をクリックして、規格番号検索で、

C 言語なら「X3010」
C++ なら「X3014」

を検索してください。

#pdf で見られますが、ディスクに保存はできないし、印刷も検索もできないと、
#使いにくいことこの上ないんですが。

2005/02/18(金) 00:34:07
こんばんわ

> ちなみに、C と C++ の最新規格の日本語版が、日本工業標準調査会
> (http://www.jisc.go.jp/)のページで、全文タダで閲覧できます。

さっそく見てみました

・・・

読み込み中・・・

完了

全571ページどどーん

と、さすが企画書だけあってすごいページ数ですね

しかしなかなかおもしろそうです
まだ軽く部分的に目を通しただけですが、なんというか、
世界が広がりそうな予感がしますね

未だ入門書ぐらいしか読んだことのないわたしにとっては
意味不明の部分がかなり多そうですが・・・それもまた勉強になるでしょう

# ただシャノンさんがおっしゃるように不便なのは確かですけど・・・
# 読み込むだけでかなりの時間が・・・

とりあえず時間があるときに少しずつ読んでいってみようかと思います
どうもありがとうございました

Ban 2005/02/18(金) 01:15:37
# 全部画像取り込みなので、例えば JISX3014 の PDF で 26MB くらいあります。
# これを毎回って.... AirH" ユーザとか眼中にないらしい。

TT414 [E-Mail] 2005/03/03(木) 12:39:24
tibさんの 2005/02/16(水) 20:00:36 の発言から
> MSDNを見たら、
> The size of the file is equal to (nFileSizeHigh * MAXDWORD) + nFileSizeLow. 
とありますが、
MSDNの記述ミスです。
正確には
(nFileSizeHigh * ((__int64)MAXDWORD+1)) + nFileSizeLow
または
(__int64)nFileSizeHigh<<32 | nFileSizeLow
であると思います。

古いスレッドには追加発言できません。