ゼロで初期化された文字列のサイズを知るには?

解決


HD  2007-12-03 07:06:42  No: 67062

動的に確保した文字列のサイズを取得するにはどうすればいいですか?
lpBuffer = GlobalAlloc(GPTR, 200); //ゼロで初期化された200バイトのメモリを確保

ここでは当然200バイトであることがわかっているわけですが、別関数に渡した後、例えば

int GetLength(LPCTSTR lpStr)
{
    return lstrlen(lpStr)
}

という関数があって、それに
GetLength((LPCTSTR)lpBuffer);

としても、当然のことですが、0が返ってきます(文字列は空っぽなんで)
sizeof(lpStr) = 4だし、sizeof(*lpStr) = 1になります。
GetLength関数の中で、200を返すようにするにはどうしたらいいですか?

GetLength関数の中をどういう風に作ったらいいか教えてください


επιστημη  URL  2007-12-03 07:10:15  No: 67063

ありません。
ポインタは始点の情報しか持っていません。


夏みかん  2007-12-03 12:41:16  No: 67064

GlobalSize でどうでしょうか。

int GetLength( LPCTSTR lpStr )
{
    return (int)GlobalSize( (HGLOBAL)lpStr );
}

http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpmemory/html/_win32_globalsize.asp


HD  2007-12-03 21:09:47  No: 67065

>>GlobalSize でどうでしょうか。

なんと!俺ってバカ(ポカポカ>自分
目から鱗です(ハァーーー  すばらしいの一言です。

επιστημηさん、夏みかんさん。
どうもありがとうございました。
見事解決しました。


tetrapod  2007-12-03 23:12:13  No: 67066

ほんとうにいいのかなー

TCHAR* p=new TCHAR [100];
size_t n=GetLength(p);
とやっても 100 は得られないよー

GetGlobalSize(GlobalAlloc(102)) としても 102 が得られる保証は無い

ポインタだけ渡して、中でサイズを得るというのは基礎設計が悪い
バッファーオーバーラン脆弱性の元


HD  2007-12-04 07:07:18  No: 67067

tetrapodへ。
>TCHAR* p=new TCHAR [100];
>size_t n=GetLength(p);
>とやっても 100 は得られないよー
どういうことですか?  自分のではうまくいってしまいましたけど?
たしかにAPI関数なんかでも、文字列へのポインタと、そのサイズも指定させる関数って多いですね。何か意味があるんでしょうか?
わざわざ引数でポインタが指し示すメモリ領域のサイズまで要求しなくても、関数の中でそのサイズを取得して、それが足りないようならエラーを返すとか、そういう方法のほうが呼び出し側からすると楽かなぁと・・

確かにGlobalSizeがGlobalAllocで要求したメモリサイズとは違う値を返すことはありますけど、これって必ずGlobalAllocで指定したバイト数よりも大きいものになってますよね。多分ページサイズ(?とでも言うんでしょうか?)とかなにかの境界までウンヌンということだったと思うんですが。
大きければ問題ないのでは?と思うのは素人考えでしょうか・・・
確保したメモリ領域よりは、少なくとも小さな領域にしかアクセスしない場合でも>バッファーオーバーランという状態になるんでしょうか?
なんか不安


YuO  2007-12-04 08:31:37  No: 67068

> どういうことですか?  自分のではうまくいってしまいましたけど?

それはたまたまです。
VC++ 2005の環境下で試したところ,0が返ってきました。

> たしかにAPI関数なんかでも、文字列へのポインタと、そのサイズも指定させる関数って多いですね。何か意味があるんでしょうか?

ポインタから有効なメモリ領域の大きさを取得することはできないからです。
有効な領域というのは物理的に有効な範囲ではなく,論理的に有効な範囲を意味します。

> わざわざ引数でポインタが指し示すメモリ領域のサイズまで要求しなくても、関数の中でそのサイズを取得して、それが足りないようならエラーを返すとか、そういう方法のほうが呼び出し側からすると楽かなぁと・・

まず,そのポインタがヒープなのかスタックなのかはどうやって判定しますか?
次に,そのポインタが指す,論理的に有効な範囲はどうやって判定しますか?

> 確かにGlobalSizeがGlobalAllocで要求したメモリサイズとは違う値を返すことはありますけど、これって必ずGlobalAllocで指定したバイト数よりも大きいものになってますよね。多分ページサイズ(?とでも言うんでしょうか?)とかなにかの境界までウンヌンということだったと思うんですが。

メモリの確保手段がGlobalAllocのみというのがそもそもの間違いです。
もっと低レベルな,VirtualAllocのような手段もありますし,ファイルマッピングでメモリを確保しているかもしれません。

> 確保したメモリ領域よりは、少なくとも小さな領域にしかアクセスしない場合でも>バッファーオーバーランという状態になるんでしょうか?

確保したメモリ領域のうち,ある一部分をオブジェクトAに,残りの部分をオブジェクトBに割り当てていた場合に,
オブジェクトAの領域に書いているつもりがいつの間にかオブジェクトBまで上書きしてしまったら,それはあきらかにバッファオーバーランです。


tetrapod  2007-12-04 08:35:39  No: 67069

> 確保したメモリ領域よりは、少なくとも小さな領域にしかアクセスしない
のであればバッファーオーバーランにはならないのでそこは心配要らない。

何が問題といって、何が何でも動的確保したメモリ領域の頭からしか使えないことがダメ
func() {
  char buf[100];
  size_t n=GetLength(buf);
}
と呼び出せないことになる。使いづらいだけで済めばいいけど、そうもいかない場合がある
MapViewOfFile のように、 GlobalAlloc でない方法で確保されたエリアを使いたい場合とか。
struct hoge {
  DWORD cbSize; // たとえばこんなメンバーやいろんなメンバーがあって
  ... // 中略
  char buf[NNN]; // このバッファへのポインタを渡したい
};
があって GlobalAlloc(sizeof (struct hoge)) すると、確保された先頭から buf は存在しない場合とか。

「確保されたバイト数」が本当に必要なの?
呼び出し側がバッファを、後からデータを連結するために大きく取ったりすることはごく普通にありうるわけだけど、
呼び出された側がその全領域に対して処理を行ってはまずいのは自明。
なので「使ってよいサイズ」を呼び出し元側が指定する構成にするほうが多いし、
たぶん構成としてもよい(よいから多用されているわけだ)


tetrapod  2007-12-04 08:40:58  No: 67070

あうー、バッファーオーバーランについては YuO さんのおっしゃるとおり。
確保された領域が全部ひとつのデータであれば心配要らないけど、必ずしもそうとは限らないわけで。


maru  2007-12-04 08:55:43  No: 67071

> どういうことですか?  自分のではうまくいってしまいましたけど?
うまくいくはずはないでしょ。

> int GetLength( LPCTSTR lpStr )
> {
>     return (int)GlobalSize( (HGLOBAL)lpStr );
> }
がうまくいくのは、この関数には渡すlpStrがGlobalAllocで確保したメモリポインタだからであって、
> TCHAR* p=new TCHAR [100];
で確保したメモリなんかわたしたら、なにが起こるかわかりませんよ。

> API関数なんかでも、文字列へのポインタと、そのサイズも指定させる関数って多いですね。何か意味があるんでしょうか?
επιστημηさんが書いているとおり、
> ポインタは始点の情報しか持っていません。
からです。


maru  2007-12-04 09:09:21  No: 67072

あれま!
コードとヘルプの確認しながら書いていたら他の人が答えてた。


HD  2007-12-04 11:06:23  No: 67073

tetrapodさん、どうもすいません。
さんを付けるのを忘れてしまいました(汗;;
>なので「使ってよいサイズ」を呼び出し元側が指定する構成にするほうが>多いし、
>たぶん構成としてもよい(よいから多用されているわけだ)
tetrapodさんの助言のようにしてみました。
確かに関数内でのメモリの大きさの把握ははっきりしますね。

>この関数には渡すlpStrがGlobalAllocで確保したメモリポインタだからであ>って、
>> TCHAR* p=new TCHAR [100];
>で確保したメモリなんかわたしたら、なにが起こるかわかりませんよ。

うーん・・・(汗;
GlobalSizeは単純にメモリの大きさを取得するのではなくて、GlobalAllocで確保したメモリの大きさを取得するってことでしょうか・・
ただメモリ領域を表現するのに、なんでそんなにいろんな方法があるのかな

でも、lstrlen関数に、スタック領域で確保された文字列と、動的に確保された文字列を渡すことと、同じような気がするんですがもしかしてコレもマズイのかな


とおり任  2007-12-04 11:29:19  No: 67074

>GlobalAllocで確保したメモリの大きさを取得するってことでしょうか・・
そのとおり。

>ただメモリ領域を表現するのに、なんでそんなにいろんな方法があるのかな
その当たりに疑問を感じるなら、C/C++はやめてVBやC#にしなされ。メモリ周りを全部自分で管理しないといけないC/C++は正直ちょっとしたアプリ開発には向いてないんで、そういう人向けにはそろそろC#とかをデフォルトにした方が良い時代って感じ?


επιστημη  URL  2007-12-04 17:43:09  No: 67075

> でも、lstrlen関数に、スタック領域で確保された文字列と、
> 動的に確保された文字列を渡すことと、同じような気がするんですが

文字列は'\0'で終端されているので、先頭から'\0'までの距離を返しているだけ。
'\0'で終端されていなければデタラメの値を返します。


夏みかん  2007-12-04 18:24:25  No: 67076

ちょっと見ないうちに増えましたね。

最終的になぜ文字列の確保サイズを知りたいの?
メモリ確保に GlobalAlloc しか使わないの?

MFC の CString クラスを使えば確保サイズを気にしないでもいいよ。
malloc、new、その他で確保した領域をポインタにセットした時点で
ポインタから確保サイズを知るすべはない。

GlobalAlloc、GlobalReAlloc で確保すれば GlobalSize で割り当てたサイズを取得可能。
HeapAlloc、HeapReAlloc で確保すれば HeapSize で割り当てたサイズを取得可能。
この2種類は確保サイズを取得する関数が特別に用意されているだけです。
でも GlobalAlloc で確保した領域を HeapSize 使っても駄目。同じ種類。

> ただメモリ領域を表現するのに、なんでそんなにいろんな方法があるのかな
メモリという資産を効率よく使うためでしょう。
http://www.geocities.jp/i96815/windows/win11.html

1...VirtualAlloc、VirtualFree
2...HeapAlloc、HeapFree
3...GlobalAlloc、GlobalFree
4...LocalAlloc、LocalFree
5...new、delete
6...malloc、free
などいろいろとあるようだ。5、6 は C/C++ 言語で決まっているため移植性が良い。
Windows OS 限定(Windows専用)なら 1〜4 を使っても良い。

1 は大きいメモリを常に使い続ける場合には有効であり、小さいサイズを確保/解放としては不向き。
2 は小さいサイズを確保/解放を効率よくするための方法。
3、4 も 2 とほぼ同じで小さいサイズの確保/解放で効率が良い。今となってはちょっと古い関数群。
Win 3.1(16ビット)時代のときは、グローバル関数とローカル関数と動作が違っていたようです。
今の 32 ビット時代では特に大きな違いはないようです。

3〜6 はプロセスのデフォルトヒープを使っているようです。
どれも良いこと、悪いこと特徴があるのです。


どら  2007-12-06 05:21:10  No: 67077

私は基本的に5か6を使ってますね・・・。
ってか、1〜4は使ったことないです・・・。

結局、動的バッファを使いたい場合、そのバッファサイズは必ずどこかに抑えておくようにするべきでしょう(文字列に限らず)。
私は必ずそうするようにしています。
これはC/C++の仕様上、仕方のないことだと私は割り切っています。


HD  2007-12-06 12:56:07  No: 67078

夏みかんさんのリンクにあるページを参考に、いろいろな種類のメモリ確保の時間を計ってみました。ヘルプにはGlobal〜関数は遅いので、Heap〜関数を使いなさい、とありますが、そんなに変わらないみたい(1024Byte * 10000回の確保)です。

ただ一つ疑問に思ったことが・・・
Byte *lpBuf;
lpBuf = GlobalAlloc(GPTR, 1); で確保して
lpBuf = GlobalReAlloc(lpBuf, 1000, 0); を何度か繰り返して再確保する
でもこれが何回目かの再確保で失敗した場合lpBuf はNULLが返りますよね。
この場合、それまでに確保したメモリ領域って解放できませんよね?

Heap〜関数の場合は、失敗した場合の返り値を定数でもらうことができるオプションがある(HEAP_GENERATE_EXCEPTIONS)ので、これを回避することはできそうですが・・


tetrapod  2007-12-06 18:38:19  No: 67079

今後永遠に Windows でしかプログラムを組むことはありえないとか
Windows と心中するつもりなら GlobalAlloc もいいだろうけど
他のOSやOS自体存在しない場合などまで考えると世界標準であるところの
malloc (C のメモリ確保) や new (C++ のオブジェクト作成) をメインに使うべきだろうな
# ClipBoard 系関数など特殊な場合を除く

Realloc についてはそのとおりで、これは典型的ダメパターン
q=realloc(p, new_size); // のように、一度別の変数に受け取って
if (!q) { ... } // error 発生!
else p=q; // 成功
とすべきだろうね

まあもっとも realloc に失敗するほどメモリを取得しちゃった場合には
エラー処理するメモリすらなく死ぬしかない、ってこともごくフツーにあるので
その辺はまたいろいろケースバイケースだったりするが・・・


HD  2007-12-06 20:23:06  No: 67080

>Realloc についてはそのとおりで、これは典型的ダメパターン
>q=realloc(p, new_size); // のように、一度別の変数に受け取って
>if (!q) { ... } // error 発生!
>else p=q; // 成功
>とすべきだろうね

そうですか。納得です。
たしかにcの標準ライブラリも使える方がいいですよね。
tetrapodさんはじめ、みなさん、たいへん長くおつきあいいただきましてあありがとうございました。

もういちど解決マーク入れときます。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加