1 :
LTD:
自分が開発途中で疑問に思ったことや
やりたい実装などを解決していくスレです。
最近気になった事は動的メモリ確保で発生する
フラグメンテーションについてで、例えばmallocは
内部でフラグメンテーションが発生しない様に
freeしたメモリ領域を結合(?)しているみたいな事を
どこかの本で見た記憶がありますが、
そうだと仮定して逆にHeapAllocでのメモリ確保はmallocと違って
そういったフラグメンテーションを一切考えてない領域の確保が
行われるのだろうか?
また、その場合フラグメンテーションを起こさないようにするには
どういった手順を踏めばいいのか興味があります。
_,ヾゝー'"'"'"ー、,; ,.:-‐―‐-.、_
,ラ 、_ ヽ,、 / \
イ r-'ー゙ "ー‐、, ミ/ ヽ
i! ,! i! ミi ,ハ i
,j i /ニ=、 ,r==、i ,,ハ ,ノヽi! ゙'レ>ヾ-、 ,!r'
i V <(・)>i i!(・)>゙!,i !!イ(・)) <.(・)>゙ i /!i
゙!ji! ., j .i_ /j i 。 。, ト-'
,ィi:. ;" ー-‐' ト' .! ,.=、 / ̄ ゙̄ー-、_
__ノ !ハ : 0 ; ,/ _,.-‐''\ ゙='' ,/
/ \\  ̄ ,// ゙ー-‐‐"
/ \.゙ー-イ ,/
3 :
sage:2009/02/15(日) 01:08:14
>>1 知ってるところまで書きます.間違っていたら指摘してください
mallocでは,OSに伺いを立てて,メモリを割り当ててもらっています.
freeすると,それをOSに返します.
その間に,たしかにメモリ領域のマネージメントが行われますが,
それはOSの責任で行われるので,プログラミングテクニックで
なんとかなる話ではないように思います.
システムコールの実装を調べてみると良いと思います.
おすすめの本は,LionsかUNIXカーネルの設計です.
(逆にいうと,私の知識はこれらの本くらいクラシックです)
あと,HeapAllocはよくわかりません.
スタックに対応するヒープのことを言っているのであれば,
mallocもヒープからメモリを取得していると思うんですが...
HeapAllocがフラグメントを考慮していないわけがない。
HeapAllocもmallocと同じく少量確保用に特化している点で同じ。
単にWin32APIと標準Cライブラリ、属すところが違うだけ。
例えば、今時のVCのmallocは単にHeapAllocを呼んでいるだけだ。
5 :
LTD:2009/02/15(日) 01:38:53
>>3 システムコールの実装ですか。
その辺は当然OSによって変わってくるんでしょうね。
因みにフラグメンテーションに興味を持ったのは、
今読んでいる本にそういう事が色々記載されていたからです。
※「C言語 ポインタ 完全制覇」(タイトルがなんとなく恥ずかしいけど。)
「HeapAlloc」がわからないというのはHeapAlloc自体が何なのかわからない
という勝手な解釈なんですがWindowsで提供されているAPIの事です。
(UNIXやLinuxを主に触っている方は馴染みがないでしょう)
上記のAPIもmallocと同じで最終的にメモリ確保を行うのですが。
Windows環境で動く処理系の場合mallocを呼び出すと最終的に
HeapAllocを呼ぶという事をよく聞くのですが、mallocは具体的に
どういう事をやった上でHeapAllocを呼び出しているのかが
曖昧で結局自分はHeapAllocを使っています。
その辺詳しく解説されているサイトがあれば教えて欲しい。。。
ところで「スタックに対応するヒープ」というのはどういう事でしょうか?
6 :
LTD:2009/02/15(日) 01:40:26
寝ます
>>5 簡単だよ。大雑把に言えばこういうこと。
HANDLE hHeap; // malloc用のヒープハンドル
void* malloc(size_t n)
{
return HeapAlloc(hHeap, 0, n);
}
3の言っている「スタックに対応するヒープ」ってのは、ここで話しているヒープのことのはず。
8 :
LTD:2009/02/15(日) 01:44:16
>>4 ありがとうございます。
「属すところが違うだけ。」
↑でなんとなく理解できました。
フラグメンテーションの結論としてはOSが管理しているから、
使う側にとってはあまり意識するものじゃないという事ですね。
(結論を出すにはまだ早すぎるかもしれませんが)
ともかく今日は寝ます
単発質問スレに対して丁寧に答える馬鹿
10 :
デフォルトの名無しさん:2009/02/15(日) 04:02:28
#include <windows.h>
DWORD test(LPVOID){return (DWORD)0;}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstp, LPSTR szCmd, int iCmd )
{
char *str=NULL;
for( int i=0; i<65535; i++)
{
for( int ii=0; ii<65535; ii++ )
{
str=new char[1024];
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)test,NULL,0,NULL);
}
}
return 0;
}
>>10 testにもWINAPI付けろ。
わざわざLPTHREAD_START_ROUTINEにキャストするな。
12 :
LTD:2009/02/15(日) 11:07:40
>>10 このコードはどういった意味があるんでしょうか?
newで動的にメモリを確保しているのはわかるんですが、
CreateThreadでスレッド生成はどういった意味を持つのでしょうか?
スタックの指定がないのでデフォルト(通常1M)のスタック領域が
割り当てられると思うのですが、通常(XP)の仮想メモリのサイズは
2Gなのでスレッド生成できる個数は2048個だと思います。(MSDNにも記載)
それに加えてnewでの仮想メモリ空間からメモリを確保(?)しているので
生成できるスレッドがもっと少なくなることの実証でしょうか?
>>11 呼び出し規約のデフォルト設定にもよりますが、
stdcallとcdeclではスタック調整のやり方が違うので
不具合の原因になりますね。
ところで少しスタックの話が出たのですが、
少し前に自動変数で全体として1Mを超えてしまう
定義をしてしまい、起動時にクラッシュしてハマった記憶があります。
色々調べた結果、スタックサイズが原因という事だったので
対処法として動的にメモリ確保かスタックを使わずstatic定義にて
モジュールにその領域分を直接埋め込むかで悩んだのですが
結局後者を採用しました。ただ、この場合は起動時に埋め込んだ領域の
初期化処理を行うはずなので、その分オーバヘッドが発生するのでは
と思うのですが、こういった1Mを超える領域を定義する場合
他の方はどういった対処で会費しているのでしょうか?
因みにある構造体を予め決められた個数分配列で定義してファイルから
全て読み込む形にしているので、全て読み込むのではなく都度読み込みに
してもよかったのですが、ファイルアクセスでのオーバーヘッドが出てくると
思ったのでstatic定義でのやり方で解決しました。
13 :
LTD:2009/02/15(日) 11:27:07
>>7 何度も読み返してみたのですが、微妙に理解できてない状態です。
特に「スタックに対応する」という辺りで自分の理解不足からか
思考停止してる状態ですorz
この際、視点を変えて
「結局malloc(VCの処理系)は内部で単純にHeapAllocコールしている」
という解釈で完結していいでしょうか。。。
ヒープかな。
17 :
LTD:2009/02/15(日) 15:13:38
>>16 HeapSetInformationなんてものがあったんですね。
ところで上記のURLの中に出てきたHeapCreateの存在は
知っていましたが、いまいち使用用途がわかっていませんでした。
なので普段GetProcessHeapでデフォルトヒープを使っていましたが
なるほど。デフォルトヒープでのメモリアロケーションは
自分が明示的に呼び出すのとは他にそのプロセスの既定処理、
つまり内部(DefWindowProcの既定処理など)でもアロケートが行われていて
その時使用するのは当然デフォルトヒープだということ、
つまりその既定処理がヒープからメモリを確保しているときはそのヒープを
ヒープマネージャ(?)が排他ロックしている為にこちらで組み込んだメモリ確保処理を
実行した場合、その排他ロックが解除されるまでメモリ確保処理の制御が
戻ってこないということになるということですか。
(マルチスレッドでは特にそういう問題がでてくるのかな。。)
そういったオーバーヘッドを回避する目的で
「他でも使用される可能性がある」デフォルトヒープを使わず
独自の専用ヒープ領域を生成(HeapCreate)して
アロケーションを行うことで排他ロックでかかるオーバーヘッドを
改善できるということですね。(少し賢くなった気がする)
ただ、これは慣れていないと使いどころが難しそうな機能だと思います。
特定のスレッドのみがその専用ヒープを使う場合はHEAP_NO_SERIALIZE
を指定すると恐らくパフォーマンスがあがるのでしょうが、
使いどころを間違えると排他を行わないのであっさりクラッシュしてしまうのだろう。。
18 :
LTD:2009/02/15(日) 15:25:47
ところで話が変わりますが
strtok()関数について
下記のコードだとクラッシュしていまいます。
char *str = "a,b,c";
char *str_ret;
str_ret = strtok(str, ",");
逆にこちらの定義だと正常に動作します。
char str[] = "a,b,c";
char *str_ret;
str_ret = strtok(str, ",");
「str[] = "a,b,c"」と「*str = "a,b,c"」では
領域の確保のされ方(?)が違うのでしょうか?
勘ですが「str[]」定義の場合はスタックに"a,b,c"という
領域の確保を行っている状態で書き換え可能なのではと思っていて
逆に「*str」定義の場合はモジュールに埋め込まれている
書込み禁止領域のアドレスが設定されているのではないかと思っています。
この辺がよくわかっていないので誰かハイエンドな方、助言してもらいたいんですが。。。
そこにハマったときに知ったんですがstrtokって渡したアドレスの特定のトークンが
見つかった場合、'\0'文字で埋めているんですね。
あとVS2005以降ではstrtok_sというスレッドセーフなモノも提供されているらしいです。
>>18 その勘で、それ以上説明のしようがないくらい当たっている。
20 :
LTD:2009/02/16(月) 02:10:43
>>19 ほぼ勘で適当な事いってみたんですがあってたんですね(´・ω・`)
かなり細かいパフォーマンスの話をするとスタック割り当て(解放も)を行う時間が
発生するから単純に書き換えず参照を行う目的であれば「str[]」より「*str」の方が
いいという認識でいいのかな。(これも適当に言ってるだけ)
ただ、この辺は処理系によっては最適かしてくれるのかなあ
>>20 その辺の知識はないが、試したら確かに半角50文字でも結構な差が出た。
ただし、よほど速度が要求される場合(ループ回数が非常に多いなど)でもない限り無視できそう。
22 :
LTD:2009/02/17(火) 01:07:35
>>21 結果というのは下記の様な感じで計測したんでしょうか?
#define HOGE_MAX 1000
static void hoge_stack();
int main(void)
{
int i;
/* GetTickCount()とか */
for( i = 0; i < HOGE_MAX; i++ )
{
hoge_stack();
}
return 0;
}
static void hoge_stack()
{
char cp_str[] = "abc";
return;
}
hoge_stackとは別に「char *cp_str = "abc";」用も用意
※投稿文字数の制限でかけなかった
直書きで動かしてないんでコンパイルするとエラーになるかもです。
23 :
LTD:2009/02/17(火) 01:19:19
よく考えてみると
static char *str = "abc";
と
char *str = "abc";
は全然モジュール生成時の組み込まれ方が違っていて
static付きの方はモジュール起動時に初期化され、
static無しの方は自動変数として扱われるわけだから
一応スタックは使用するということになる気がする。
但し、無しの方も有りの方も参照する部分は
「モジュールに既に組み込まれている」と考えて、
後はスタックでアドレス参照を設定するのか
モジュール起動時の初期化でアドレスを設定するのかの
違いということになるのかな。
細かなオーバヘッドを考えると例えばある関数のローカル変数宣言の定義で
「char *str = "abc";」という定義があった場合、
その関数を複数呼び出すのであれば呼ばれるたびにスタック初期化する
static無し定義よりモジュール起動時に初期化(アドレス設定)される
static付きでの「static char *str = "abc";」の方が効率がいいという結論になるのか。
と寝る前に考えてみた。。。それにしてもstatic定義って実に興味深い。
見当違いであれば指摘してください。寝ます。
先ず、char * foo = "abc";とchar fooz[] = "abc";では何が違うのか、おさらいしておくことをお勧めする。
突き詰めるならアセンブラ出力ぐらいは見るべき
27 :
LTD:2009/02/17(火) 19:50:54
>>25 hogeじゃなくてfooとは中々・・
その辺少しおさらいしてきます。
>>26 なるほど。やってみます。
今日は勉強の事は一切忘れて好きなことやろうかと。
なのでその辺の検証はまた明日。
28 :
LTD:2009/02/18(水) 23:03:21
char str[] = "abc";
00411B3E mov eax,dword ptr [string "abc" (41573Ch)]
00411B43 mov dword ptr [str],eax
char *str = "abc";
004113DE mov dword ptr [str],offset string "abc" (41573Ch)
str[]の方はやはりスタックに領域を確保して
モジュールに組み込んである"abc"をコピーしていた。
*strの方はそのままモジュールに組み込んである
文字列リテラルのアドレスを設定していた。
やはり「参照するだけ」の目的であれば「*str」で定義していた方が高速らしい。
ところで上記はたまたま「abc\0」の4byteだからだろうが
dwordでコピーしてるのに笑ってしまった。これが最適化か。
因みに
「static char *str = "abc"」
と
「static char str[] = "abc"」
の設定は同じように起動時に初期化されていたけど
文字列リテラルアドレスを直接設定されているのは前者で
後者ははやり領域をどこかに確保しているみたい。
モジュールサイズで言うと前者の方が小さくなるという事なので
やはり参照のみでの使用を行うのであれば「*str」の方が
総合的に高速だったという結論に達しました。
何か補足、指摘があれば言ってください。
29 :
LTD:2009/02/18(水) 23:10:32
今までアセンブリはかじった程度の知識しかなかったけど
今回の細かいところでの高速化には非常に惹かれるので
これを機会に本格的なアセンブリの勉強しようかと。
ただ、今読んでる書物を中途半端に終わらせるのは
気に食わないので読み終えてから取り掛かる事にします。
また、今現在読んでる「ポインタ完全制覇」で疑問が出た場合は
それを議題としていく。そんな方針で勉強していきます(´・ω・`)
31 :
LTD:2009/02/20(金) 21:16:57
>>30 ソースみました。
少し実行してみましたが0xFFFFFFFに負けました(´・ω・`)
非常に興味深い作りなので解析させてもらいます。
32 :
LTD:2009/02/26(木) 23:00:21
「C言語 ポインタ完全制覇」にて下記の様なコードがあった。
#include <windows.h>
#include <stdio.h>
typedef enum
{
READ_LINE_SUCCESS,
READ_LINE_EOF,
READ_LINE_OUT_OF_MEMORY
} ReadLineStates;
extern ReadLineStates LineData( void );
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
int i_rinf;
i_rinf = LineData();
printf( "%d\r\n", i_rinf );
return 0;
}
ReadLineStates LineData( void )
{
return READ_LINE_SUCCESS;
}
33 :
LTD:2009/02/26(木) 23:07:49
typedefで型を作って
その型を関数の戻り値として
プロトタイプ定義させるという手法は始めてみた。
はじめはそのenumで定義された戻り値以外を指定すると
エラーになるのかなと思って上記のコードを書いてみたけど、
そうではないらしい。
そもそもこういう場合enumはint型の戻り値として認識されるのだろうか?
あまりenumは使った事がないからその辺曖昧です。
ただ、今まで使った事がない分、中々斬新な使い方だと思いました。(´・ω・`)
因みに229ページ
>>33 enumを戻り値にするのは結構あると思うよ。
0から順番にintで割り当てられるんだったとおも。
35 :
LTD:2009/02/26(木) 23:52:44
>>34 そうなのですか。
自分は仕事でもそういうコードに出会ったことがなかったもので。
でもこのやり方は結構使えそうですね(`・ω・´)
この本で一番の収穫というか自分が欲しかったけど
今まで探してもあまりそういう情報が載ってなかったものが
252ページに載っていました。
ヘッダファイルの書き方についてです。
書き方というより考え方としてどういった事を
原則として書く事についてなのですが。
この本に記載されているものを引用させてもらうと
1.すべてののヘッダファイルには、二重インクルード防止をかけること。
2.すべてのヘッダファイルは、単体でインクルードできるようにすること。
という事が記載されていました。
前者に関しては暗黙のルールとして使っていたのですが、
後者については「そうしなければいけないんだろうなあ」と思いつつも
時と場合によってそういうやり方をせずに実際使うソースファイル内で
インクルードしまくりなところが自分にはありました。
なのでこれからは上記の二つは必ず念頭において
ヘッダファイルを作る様にすることにしようかと。
因みにこの本に載ってなければこのスレで他の人がヘッダファイルを作る時
どういう事を考えて作っているのかという事を話題にして意見を募ってみようかと
考えていました。(´・ω・`)
>>35 --
typedefで型を作って
その型を関数の戻り値として
プロトタイプ定義させるという手法は始めてみた。
--
それは見聞が狭すぎる。ちょっとしたライブラリのソースを見れば簡単に見つかると思うが。
つーか、身近なところではfopen()だってそうじゃないか。
enumに限定しても、ステータスを返すタイプの関数ではしばしば見られるぞ。
まぁ確かにロートルはenumを使わずにマクロを使いたがるが、両者を較べればどちらがいいかは自明だろ。
--
#define SOME_LIB_OK 0
#define SOME_LIB_WARNING -1 /* ロートルは異常値を負にしたがる */
#define SOME_LIB_FATAL -2
int someLibEntry();
--
typedef enum {SomeLibOk, SomeLibWarning, SomeLibFatal, NofSomeLibStatus} SomeLibStatus;
SomeLibStatus someLibEntry();
37 :
LTD:2009/02/28(土) 01:13:21
>>36 文言をあまりに簡略化しすぎて語弊があったかもしれない。
>typedefで型を作って
>その型を関数の戻り値として
>プロトタイプ定義させるという手法は始めてみた。
正確に言うとenumをtypedefで切って
戻り値として使用しているという意味で
typedefで作った型のポインタを返すような
関数は自分もよく使っていたりします。
自分も今まで戻り値をデファインで定義して使っていました。
何か特に理由があったわけでもなく
書物で負の値を戻り値として使っているのを
よく見たので自然とそうなっちゃったのかもしれない。
でも言うとおり使いやすさ追いやすさでは
enumを使った方がいいですねー。
38 :
LTD:2009/03/02(月) 00:23:11
「C言語 ポインタ完全制覇」全部読み終えました。
途中筆者の感情が垣間見えてた部分はありましたが。
個人的には良書でした。
次やる事はアセンブリを本格的に勉強しようと考えていましたが。
かなり方向転換してTCP/IPとデバイスドライバ開発に関しての
勉強をやって行こうかなと思っています。
しかし、自分はTCP/IPの知識とデバイスドライバの知識が皆無で
まったくゼロからの勉強になりそうです。
TCP/IPに関してはWEBである程度の資料がありますが、
デバイスドライバ開発の情報は日本語サイトがほぼ
皆無なのでどうしようかと。。。(中学レベル以下だけど英語覚えるか・・)
ともかく今日は「マスタリング TCP/IP」という本を購入しました。
事前に下調べもせずたまたまこの本が目に留まって購入したので、
もっといい本があるという方は教えていただけると助かります。
同時にデバイスドライバの本も少し前に
「WDMデバイスドライバプログラミング完全ガイド(上・下)」を
購入しています。(まったく読んでません)
これに関してももっといい本があれば教えて頂きたいです。
>>37 負の値を返したいだけなら、enumでもできるぞ。
typedef enum {SomeLibOk, SomLibWarning = -1, SomeLibFatal = -2} SomeLibStatus;
人間が数を管理する愚は避けられないけど。
40 :
LTD:2009/03/04(水) 23:54:37
>>39 そういう使い方もありますね。
でもやっぱり明示的に値を指定よりは
デフォルトで設定されている値を使うという
やり方のほうが間違いを防止という意味でよさそうです。
ところでデバイスドライバを作るとして
普段使っているCreateThreadやWaitForSingleObjectなどのAPIは
はカーネルモードで動くデバイスドライバで使う事はできるのでしょうか?
デバイスドライバ開発する場合、一部のAPIに関しては
RtlCopyMemoryとかカーネルモードAPIを使わないとダメだという
制約があるのではないかと思っているのですが。
つ PsCreateSystemThread
つ KeWaitForSingleObject
42 :
LTD:2009/03/05(木) 23:30:01
>>41 ありがとうございます。
今日PsCreateSystemThreadについて
少し調べてみました。
スレッドを生成する対象プロセスの指定が
できるというのはかなり強力な機能ですね。
ネイティブAPIフックとか使うと色々できそうだ。
でも、ブルースクリーンの嵐だろうからテストは
VPCでやらないといけないんだろうな。(´・ω・`)
>>42 プロセス指定だけならユーザモードでもCreateRemoteThreadでできるよ。
44 :
LTD:2009/03/06(金) 00:02:14
>>43 なるほど。確かにこれを使えば別プロセスにスレッド生成できますね。
自分がCreateRemoteThreadを使ったときはDLLインジェクションやる
為に使っていたので当時はあまり深く考えていませんでしたが
よくよく考えてみれば確かにその為のAPIですねこれは。
45 :
LTD:2009/03/06(金) 00:13:18
流れがかなり変わりますが、
プロトコル(TCP/IP)の勉強は少しずつやっています。
今プロトコルの基礎中の基礎を勉強していて、
ちょうど今プロトコルのモデルとしてよく使われるらしい
OSI参照モデルについての概要把握をやっています。
まったくではないにしても前知識がないためかなり苦戦を
している状況ですがなんとかやっています。
もうしばらく進んだら不明な部分をこのスレで煮詰めていこうかと。
ただ、デバイス関連だったりTCP/IPだったりすると
混乱しそうなのである程度進んだ段階で煮詰めていく部分を
どちらか一方のみにすることにします。
当分の目標はこのスレを自分が続ける事です。
こやつめw
なんなのこのスレタイ。
「なんでだろう」の[疑問編]って。
そのうち「なんでだろう[風雲龍虎編]」でも始まるのか?
48 :
√:2010/05/25(火) 23:52:28
【`・ω・´】【`・ω・´】【`・ω・´】
このスレッドは天才チンパンジー「アイちゃん」が
言語訓練のために立てたものです。
アイと研究員とのやり取りに利用するスレッドなので、
関係者以外は書きこまないで下さい。
京都大学霊長類研究所
50 :
デフォルトの名無しさん:2010/07/29(木) 13:05:54
アイちゃんお元気ですか?
52 :
デフォルトの名無しさん:
part2まだですか