C言語相談室(上級者専用)
■ このスレッドは過去ログ倉庫に格納されています
RustはCの「後継」であり、C++の「代替」
これでRust周りの(宗教戦争じみた)論争の理由が理解できた。 ポインタってさ、「指示子」と和訳したら分かりやすいと思うんだが
そうしなかった理由ってなんだろ
それとも俺の感性がおかしいだけか え? なに? もしかして昔なにか荒れる原因になった ANDOR 問題のある人が使ってた用語なのか。
若いもんで知らなかったわ すいません。 「指示子のgcc拡張」みたいな早口言葉モドキができて面白いかも。 指示子の指示子
指示子の配列
配列の指示子
配列の指示子の指示子
指示子の配列の指示子
ダブル指示子
配列の指示子の配列 👀
Rock54: Caution(BBR-MD5:1341adc37120578f18dba9451e6c8c3b) C99(ISO/IEC 9899:1999)で,main()関数の型がintな理由ってどこかに書いてあるっけ。
今ふと,リターンコードって0から255の整数なんだから
uint8_t main(...
としても問題ないよなと思ってさ。 すまん。途中で送信してしまった。
そうするとコンパイラにmain()の型はintだと怒られた。 型がintというのは決まってる。
実際の範囲は実装依存。windowsだと違ってなかったか? main()が戻す値は呼び出す側の問題だとは思うが、その部分(crt0とか)は普通はC言語に合わせてintを
返してくると想定して作られていると思う。しかしその部分まで自作するというのであればなんでもアリではある。
https://en.wikipedia.org/wiki/Crt0 シェルが受け入れるコマンドの終了ステータスの値が0-255の範囲、
てのはUNIX系もDOS系も(珍しく)一致してるのね。
unsigned char の外に出ないことは間違いないわね。
ANSI以前の古いCでの「宣言なしに使われた外部関数はintの値を返す」
という仕様が、規格化したときに互換性の問題を生まないように
main()の返り値はintと決めたんじゃないかしら。 なるほど。まあ過去互換性は重要だしね。
ただ,CはPOSIXというOSの標準規格を定めてる団体が関与して設計されてるから「リターンコードは0--255。よってmain関数の型はuint8_t」と割り切ってくれてもいいのになぁ。
とか勝手に思ったりしてる。 まあintが妥当だろう。
しかし、_exit()に渡すのがintでwait()した時も一応exit statusはintだよな。
どこで上位ビット欠落させてんだ? >>69
OSの中だ。_exit() はシステムコールだし。
まあでも UNIX 系 OS じゃないならこれは違っているかも知れない。 ちょっとCの範疇を越えた話になるけど
リターンコードが0--255ってどういう段階で決定されたんだろう。
DOSとUnixが同じ範囲のリターンコードを持ってるって、偶然とは考えがたいんだけども DOSの終了ステータスはUNIXの仕様をそのまま使用だと思う。
UNIXの方は、子プロセスを作って別の仕事をさせるって定型処理で、
「親プロセスで子プロセスの終了を待つ」ためのwait()系の関数が、
・子プロセスが正常終了した場合は子プロセスの終了ステータス
・子プロセスが割込みで中断された場合は割込みの種類
という2つの情報を1個の返り値で戻すことと関係あるのじゃないかな。
1個の整数値を上位と下位のビット群に分けて別の情報として使うために
それぞれの情報量を1バイトずつに制限した、と。 auto変数で char配列を可変長で動的に確保する方法は無いかな。
アセンブラレベルならスタックポインタを操作すれば可能だと思うが。
文字列処理の内部バッファとして入力に合わせたサイズを確保したいんだが、とりあえず今は固定長バッファとそれを超える場合は malloc でやってる。 >>75
gcc使うなりalloc()使うなりすればいい
とりあえず
vla c言語
で、ぐぐれ >>75
alloca
標準ではないけどほぼ標準扱い。(殆どの環境で使える)
ただし realloca は無いので注意。 >>76-77
ありがとう、まさにぴったり。
言語レベルでの可変長配列は C11 から(オプションだけど)入ってるんだね。
90年代からメンテされてるコードだからそっちは使えなそうだけど、alloca を検討してみる。 >>79
環境わからんからなんとも言えんが組込みとかWindowsとか意外にスタックサイズがしょぼい環境あるから気を付けてね いやいや、C99からだよ。C11でオプションになった。答える方はそのくらい知っとけよ。
まあ入力が小さいのが予め分かってないと使いにくい機能だよ。 発端の >>75 の意図次第だが、free()しなくても安全に蒸発して欲しいとか、
ヒープの確保と解放の処理時間を嫌っての話ならgetline()はちょいと違うね。
文字数の予測が難しい入力を扱うにはとても便利なんだけど。
それにしても「上級者専用」って看板が架かってることを意識すると
このスレッドは書き込みにくいな。気後れしちゃう。 動的確保が無難なんだよね。最後にfreeすればいいだけだし。
最近まで知らなかったんだけどscanfで%msで動的確保してくれるの便利だな。scanf自体はなかなか難しくて使いづらいが… >>81
あ、C99で入ったのをC11でオプショナルに格下げ(?)されたってことか。
処理系によっては厳しい実装なのかな。
やりたいことってのは文字列のエスケープを含んだ組み立てなんだけど、使用するエスケープ関数がありものでその仕様は入力文字列長に対して2倍以上の出力バッファを与えないといけないことになってる(出力サイズを指定して打ち切らせることができない)。
実際に2倍に膨らむことは稀だし、エスケープするのも文字列中の一部なので、自分の処理で最終的に出す出力結果は論理最大よりかなり小さくなる(自分の処理は指定された出力長で打ち切る)。
まず来ることが無い事態のために論理最大の配列を取っておくのもどうかと思い、いい方法があるかここで相談させてもらった。
しかし想定外のことが起きちゃった時にどうなるべきかを考えてどうするかを決めるから、もしかしたら動的配列の意義が無くなって別の方法にするかもしれない。
この処理自体は高頻度で呼ばれるから、高速で省メモリそして内部的な都合での打ち切りは極力避けた作りにしたいって感じ。
いろいろコメントありがとう あらかじめプールしておいて使いまわす
足りない時だけ臨時で増やす ReactOSというOSでフォントエンジンの改良を行っているが、
Google Chromeというブラウザでおかしくなる。
何故かEIPレジスタがゼロになって、初回例外が発生する。KDBという付属のデバッガでトレースしたが、
どこの関数で例外が発生しているかわからない。
たすけて。。。 >>85
> 動的確保が無難なんだよね。最後にfreeすればいいだけだし。
結局の所、結論としてはそうだね。
>>86
多分素直にmallocする関数をラップして使う方がいい。
仕様の動向自体は知らんが、
> 処理系によっては厳しい実装なのかな。
技術的にはこれはない。alloca相当(ポインタ相当)にすればいいだけなので。
ただ、間接アクセスになるから速度は落ちるし、
mallocに対しての利点は『自分で』freeしなくて済むことくらいしかない。
(『自分で』ソースに書かなくてよくなるだけで、速度上のメリットはない。
reallocaが無い為、最初のバッファ(=サイズが不明)はヒープ上に動的確保するしかなく、
自分で書いてなくてもどこかでfreeされてるだけだから。なら最初からgetlineでもいいし)
ただ、そちらの実装は(おそらくバッファオーバラン対策で)一旦固定長バッファに取り込み、
その後alloca領域に対してのコピーか?
ならまあ一応freeしなくていい速度上のメリットは残るが、
一般的にはスタックサイズ管理のコストが増える方が問題とされ、その実装はないとも思うが。
結局、allocaもイマイチなんだよ。だから標準にもなりきれない。 >>83
>>1,13読んで以下にどうぞ。
C言語なら俺に聞け
https://mevius.5ch.net/test/read.cgi/tech/1534430162/
>>12
>>89
それがこのスレの正しい使い方かもしれんね。
ぱっと見て分かるものでもないけどさ。 allocaとか動的サイズ指定の配列はスタックだから基本的にはmallocするよりは速いよ。頑張っても同等。
繰り返して呼ぶなら差が出るかもね。 ああ、言い直しておくよ。
allocaとmallocなら、
確保:allocaの方が速い
使用:同速(ただし初回からキャッシュが当たる分allocaの方が速い)
解放:freeの必要が無い分、allocaの方が速い
(ただし実装によっては上位でfreeしてるだけであり、同速)
管理:通常、ヒープサイズ>>>>>>スタックサイズの為、サイズ管理が必要
速度差が見えるような使い方が出来るのなら、ある意味大したもんだと思うよ。 スタックって確かスレッド毎に肥大化してくよね?
で、肥大化してもスレッドが停止するまで縮小しない
あってる? 関数から戻るとき(エピローグ)にスタックは縮小することがある。 実際のx86 CPUでスタックポインタを表しているのが、ESPレジスタの値。スタックサイズの増減はESPの書き換えに過ぎない。 流れのついでに聞いてみたいけど、malloc はスレッドセーフでしょ。
てことは内部で排他をかけてると思うけど、となるとマルチスレッド下で malloc や free を多発させるとパフォーマンス的に良くなかったりしないかな。
言ってなくてごめんだけど、今回の処理はマルチスレッドで動くんだ。
なのでバッファはスタック上に取れると都合がいいという事情もあるし、特別な初期化手順や終了手順も用意したくないから事前に malloc して使い回すってのもやりづらい。
ちなみに動作環境は x86 linux だから、alloca は SP をズラすだけの非常に高速な実装になってるんじゃないかと想像してるから、関心は高い。
でも、最悪でもスタック上に収まるバッファという前提にするなら最初から論理最大サイズ固定のバッファで良くね?なんて話もあるから、実際にどうするかはこれから検討。 glibcのmallocならサブarenaから獲得してくるから性能問題にはならないらしい >>99
サイズの問題がないのならallocaを使うことに問題はない。
mallocより遅くなる理由もないので。
ただ、普通に組めば分かるが、
> malloc や free を多発させる
ってのがあり得ない。
仮にこれがallocaで有効に代用出来るとするなら、再帰下降パーサ等、「再帰」が必要になるが、
再帰下降パーサの場合はインミュータブルでよく、元の文書をオフセット付きで参考して終わりだ。
再びallocaすることはない。
同様に、ループでパースするのなら、ループの外でmallocして十分な領域を確保し、
そこに上書きで使うことになる。だからmalloc/freeは1回ずつで済む。
もう一度言うが、allocaはスタックだから、「再帰」しないと領域を追加出来ない。
この使い方は普通無いし、君もやって無いと思うよ。
でも、普通のmallocを全部allocaで代用しても、サイズ以外の問題は無いから、可能ならそれでもいい。 > alloca は SP をズラすだけの非常に高速な実装になってるんじゃないかと想像してるから
これはその通り。
> 最初から論理最大サイズ固定のバッファで良くね?
これもその通り。上記ループなら普通これで行く。
それがスタック上で問題になるサイズならmalloc/freeが1回ずつ必要になる。
だから君の今の実装>>75もさほど悪いわけでもない。
今風の「実装を外に漏らさない」方針なら子関数でmalloc/freeやallocaすることになる。
おそらくそれで考えているのだと思うが、元々のCの思想はそれとは違い、
char* buff = (char*)malloc( ... ); // または char buff[2048]; 等
while (....) {
parse_func(buff, .... );
}
free(buff);
として外側で確保し、それをparse_funcに渡す。
これにより、変数の寿命とスタックの動作を一致させ、freeし忘れもなくなる。
この方法だと、allocaで毎回「SP をズラすだけ」すらする必要なく、parse_func内は最速になる。
(allocaを毎回繰り返すよりも速い) あくまで処理系依存の話として…
マルチスレッド固有の問題はほぼない気がしますね。malloc/freeは単に別の領域確保していくだけだしスタックの場合はスレッド生成時に確保すると。
で、まあmallocの実装はそこまで悪くないと思うけどスタックが速いし、さらに静的領域の方がちょっと命令数は少なくなるでしょう。 >>102-103
いろいろありがとう。材料は揃ってると思うので、最終的にどうするかはこれから決めるよ。
>>104
malloc は内部的にはヒープから領域を切り出してくるわけで、切り出したチャンクは恐らくリンクかなにかで管理してるはずでしょ。
これはプロセス単位で一式だから、マルチスレッドでよってたかってこの構造を更新するには排他は欠かせないと思うんだがどうなんだろ?
切り出されたメモリがマルチスレッド下でどうかという話ではなく。
>>100 の内容はちょっと分からないから調べてくるが、結局は誰かが排他してるんじゃないのかな。 >>96
実行環境依存だが、win/linuxならその認識で良い
allocaは便利だがスタックの肥大化を加速させるので
環境によっては注意が必要
>>105
興味があれば
https://youtu.be/0-vWT-t0UHg >>105
ナイーブなmallocの実装はそうです。中央集権的。同時に動くのは1個だけ。でもクリティカルセクションはそんなに広くないかも?
まあ実装はいろいろあります。 >>106
すげーおもしろかった!
マルチスレッドでどうやってるかについても分かったよ。
過渡期の性能を犠牲にして使っていくうちにいい状態に収束するようにしてるのね。
しかし malloc のコードが 5000行てw
ただ、mmap すればメモリ管理のコストが小さい的な言いぶりはどうかなって気はするな、動画の中でもツッコミ入ってるっぽいけど。
結局カーネルだって何らかの形でアドレス空間の空きを検索するし、アドレス空間を割り当てる処理にしても排他はかけてるはずだから、それなりのコストはかかるし競合の問題もあるよね(ユーザーがやる処理よりも高効率だろうと思うけど)。
>>105
malloc の話を持ち出したのは、排他とかで結構遅いんじゃね?ってのが出発点なので、
排他はしてるって言うし mmap だからという説明じゃ解決って話でもないかなって感じ。
>>109
>>75 の頃の時点では K&R アロケータ程度の認識だったからマルチスレッド下ですげー遅そうな印象だったけど、さすがによく考えられているということは分かったよ。
ちなみにこれも読み始めてた。
https://www.valinux.co.jp/technologylibrary/document/linux/malloc0001/
>>106 の動画を見てだいぶ見通しがよくなった。 POSIX準拠を念頭にC言語を書かれている方に訊きたいのですが、機能検査マクロって実際どのように使われていますか
#define _POSIX_C_SOURCE 200112L
#define _XOPEN_SOURCE 600
↑僕はこのくらいであればシステム互換性は担保されると思ってるんだけど
未だにPOSIX 1998にしか対応してないOSやコンパイラとかあるんですかね。
少なくとも_XOPEN_SOURCEが600以上じゃないと<math.h>においてM_PI定数が扱えないので
数式処理のプログラムをよく書く身としてはちょっと困るんですけども……。 好きな人が古い環境を使っているという事情でもなければ、単に無視すれば良いのでは >>114
CMakeで環境を判定して、マクロを定義するのが王道だと思う。 #ifdefでM_PIがないときだけ自分で定義するのが手っ取り早いと思うけど。 >>115
乙。てかreactOSって使い物になるのか?
無理してWindows使うよりは、素直にLinuxに逃げるのが順当だと思うが。 >>119
使い物にするのが、依頼人の要望だから、私はそれを、果たすのみ。。。 上級者ではないんだけど 経験豊富な人に訊きたいのでここに書きます
https://git.musl-libc.org/cgit/musl/tree/include/stdlib.h
↑ここなどを見るとEXIT_SUCCESSは0で固定されています(OSによる場合分けがない)。
ということは少なくもMusl LibCプロジェクトは正常返り値を0と決めてかかっているということでしょう。
WindowsやUnix系のOSでは正常返り値は0ですが、ほかの値は絶対に考えなくていいのでしょうか
みなさんはいままで正常返り値が0でない処理系(というかOS)を見たことなどありますか。 >>121
もしプロセス終了コードの具体的な数値(数値かどうか)が異なるシステムがあるなら、
正常終了を示す main() の戻り値 0 をその環境用の終了コードで成功の値にマップするのは
そのシステム向けの処理系の仕事になる。
成功・失敗の区別以外に興味がない限りCのコードを書くプログラマが気にする必要はない
と規格は想定している。 >>121
おまいWindowsでexe実行したとき必ず戻り値みてるか?
BATとかあるけどさ >>123
なるほど crt0あたりでmainから0が来た場合の本当の成功判定を処理してるってことですね。
ありがとうございました。 せっかく上級者が集まっているんだし、評論でもやりましょう。
https://github.com/reactos/reactos/blob/master/win32ss/gdi/ntgdi/freetype.c
このソース、俺の担当なんだけど、ひまだったら
喧嘩上等で悪い所・直すべきところを全部指摘してくれ。頼むよ。 頑張ろうと思ったんだが、暇つぶしでやるには長すぎて挫折した。すまん。 >>126
IntCharSetFromCodePage の for の中のひとつ目の if は削除して、for に入るまでに
if( uCodePage == 0 ) return DEFAULT_CHARSET;
をやる。
for に入るまでの if は 確率が高い順に並べる。
とか? >>126
表面的になぞっただけで、意味は読んでいないから参考程度で。
L924-931、FW_*のenumに揃えられるのなら揃えた方がいい。あと、ベタで書くよりはループが良いかも。
L946関数他、InterLockを外部的に明示的に行っているけど、俺ならアクセス関数内に押し込む。
ただこれは以前揉めたから、他の流儀の奴もいるのかもしれない。
俺の考えとしては、enumやdefineする理由は、変更箇所を1ヶ所に留める(同期させずに済む)事だと考えているので、
外部的に明示的にInterLockをその都度行うのは、変更時に全ての場所を同期させる(同様に書き換える)必要があるという意味で悪手。
敢えてInterLockしていることを誇示したい等の他の理由がなければ、アクセス関数内に押し込む。
L2024,L2040,L2052,L2135、同じ事を4回書いてる。
おそらく全体的に低レベルまで全部触りに行く、ある意味Cに典型的なコードになっている。
嫌う人もいるとは思うが、俺ならこの4回をどうにか1つに纏めて抽象度を上げていく。
4つの*NameWを常に同時に使うものなら、普通はstructにする。
L2879とL2888で、FT_Done_GlyphとDPRINT1の順序が逆。
お互い干渉しないから問題なしなのだろうけど、可能なら順番は揃えた方がいい。
しかもL2926とL2934でも順番が逆なので、L2869-とL2923-ってコピペしてるだろこれ。
そういうのはマメにサブルーチンとして切り出すべし。でないと抽象度が上がらない。
L3567-L3581、同じ事を4回やってる。サブルーチンに切り出して xx=sub()の形で書くべし。
あと、リテラル(初期化構文)使えるのならリテラルで書いた方がすっきりすると思うが。
L5081-5112、悪いとは言わないが、どうにかならんのか?
失敗系の速度が大して必要ないなら、ReleaseAllResources()で if (NameInfo1!=0) ExFreePoolWithTagの様にして纏めるとか。
なおL5153、"WithTag"が無いバージョンを呼んでいるが大丈夫なのか?
可能であればL5162で関数切って2つの関数にした方がいいと思う。(リソース確保/失敗系と成功系を分ける)
L5889とかもそうだけど、おそらく全部のメンバを初期化してるだろ。だったら初期化構文使った方が見た目分かりやすい。 ただし、上記は「ソースを綺麗にする方法」であって、通常は速度は落ちるので注意。
酷い話、ベタで書きまくっている方が速いのも事実。
あと、既に言ったが、上記はなぞっただけであり、関数内しか見てないので注意のこと。
本当に問題なのは関数間であり、それはガチで時間をかけて読まないと分からないから、そこまではやる気無い。
ただ、この雰囲気なら、2割くらいの関数は整理(削除)出来るのではないかと思うけど。
自分でも気になっているところがあるのなら、それを先に言うべし。見るから。 気になってることと言えば、TextOutでOPAQUEモードのときに背景を塗り潰さないといけないのだが、それを一個の長方形でいっぺんに出来ないかな。 >>133
とりえあず行番号と関数名を言え。
それとも、そのソースではない一般の話?
(Ctrl-Fでは6ヶ所引っかかるが) ちなみに、これって、実際にフォントをレンダリングしているんだよな?2次ベジエ等で。
なら、OPAQUEの場合は先に塗りつぶしておいた上に描いた方が楽だと思うけど。
再帰等の条件に描画情報を使っていてそれが出来ない場合、反転でAND取ってORすれば昔のPSETにはなる。
(フォントの黒部分にもAlpha値があるのなら全面的に計算するだけだから、これではなさそうだし) >>134
GreExtTextOutW関数のL5847...L5954です。 >実際にフォントをレンダリングしているんだよな?2次ベジエ等で。
FreeTypeというフォントレンダリングライブラリでビットマップとアウトライン曲線を取得しています。
>OPAQUEの場合は先に塗りつぶしておいた上に描いた方が楽だと思うけど。
そうしてます。 >>137
> それを一個の長方形でいっぺんに出来ないかな。
L5853のfor文が気になっているのなら、多分このCountは文字数で、
英文の場合は文字種類間ごとにピッチを変えるからこうなっているのでは?(いわゆるプロポーショナルフォント)
1回の操作に纏めたければ、文字単位でビットマップを生成して重ねるのではなく、
あらかじめ全体のビットマップを作ってそこにレンダリングしていけばいいのだけど、
速度的には速くなりそうだけど、後々面倒だから文字単位にしているのではないかと。
ここを文字単位でしこしこやるのは、悪いことではないと俺は思うけど。
気になっているのは被る部分が2度レンダリングされるって事かな?
幅5pxで1px被ってたら20%高速化だから地味に大きいのも事実だけど。
書き換えたいのなら後述参照。
なお、本筋とは異なるが、L5885-5887, L5926, L5938は正常系でもDPRINTしてるが大丈夫なのか?
DPRINT自体がリリースビルドでは消される仕様なら問題ないが。
>>139
正直なところ、指摘は参考に留めて、必要ない限りあまりいじらない方が良いとは思う。
これは昔の「動いているコードを触るな」であり、今風の「積極的にリファクタしろ」ではないが、
リファクタはデグレードをガッツリ検出出来る環境があればこそであって、
例えばchromeとかはそれをガチガチにやっているから出来るのであって、
検証環境のサポート無しなら、バグだと分かったところだけ修正し、そのついでに書き換える程度の方がいいと思うよ。
それで、該当部分を書き換えたいのなら、俺なら以下の手順でやる。(自己でデグレードチェックをする)
1. 対象部分、5847-5954を関数に切り出す。
2. その関数と全く同じ出力を生成する新関数(書き換えて1回でレンダリングするもの、おそらく20%程高速)を作る。
3. 両方とも呼び出す形でデバッグビルドし、結果を常に比較する状態でしばらく動かす。
具体的には、旧関数出力と新関数出力のビットマップを全比較する。
フォントのレンダリングなんてものすごい回数行われるので、バグっているようなら大抵これで検出出来る。
高速化は結構すると思うから、書き換える意味はあるとも思うけど。やるなら頑張れ。 >>139
はえーよ
>>140
第一段階としてはそれで良い。
(以下は改変後の行番号として)
ただ、オブジェクト指向的にはL2033-2036の中身をいちいち見せてNeededに足すのは悪手で、
可能なら AllLengthというプロパティを作りたいところ。
Cなら関数FONT_NAMES_get_all_length()とかで「中身を知らなくても全部の長さが分かる」様にして疎結合化する。
(後々fontNamesにメンバが追加されても struct FONT_NAMES の変更だけで済むようになる)
それで、L2125-L2141でご丁寧に全部Otm(の後ろ)にコピーしてるだろ。
これだと、多分、本来はOtm内に FONT_NAMES 構造体を持ち、そこに構造体のコピーで済むように書けるはず。
具体的には、
Otm->FontNames = FontNames; // --- (α)
みたいな。 ただこれ、データ埋め込みだから Otm->otmFamilyName等はオフセットで管理し、
不要なUNICODE_STRING構造体部分は破棄して文字列実体だけコピーするケチケチ作戦か?
なら、普通は文字列実体のコピー関数をメンバ関数として用意する。Cなら、
char* UNICODE_STRING_copy_string(UNICODE_STRING* ustr, char* Cp){ // --- (β)、一応thisを第1引数にした
wcscpy((WCHAR*) Cp, ustr.Buffer);
return Cp + ustr.Length + sizeof(WCHAR);
}
そしてL2124-2126を
Otm->otmpFamilyName = (LPSTR)(Cp - (char*) Otm);
Cp = UNICODE_STRING_copy_string(FamilyNameW, Cp); // --- (γ)
として、実装をはがしていく。(疎結合化する)
これで UNICODE_STRING 内にメンバ Buffer と Length があることを知らなくてもコピー出来るようになった。
同様に、Otm内に直接FontNames(または文字列実体だけコピー済みのchar*)を持つようにして、
FontNamesと同じ型を使い回せるようにすれば、もっと記述は少なくて済むし、疎結合化していく。(α)
ただし、ここら辺がLinusが嫌う、C++が逆に結合していく部分であって、
コード上の静的コールグラフでは疎結合化するが、型を通して知識的に密結合してしまう、というところ。
具体的には、FontNamesで綺麗に書こうとすれば OUTLINETEXTMETRICW に FONT_NAMES を持たせるべきだが、
これをやると、OUTLINETEXTMETRICWを使う人は全員FONT_NAMES構造体を知っていけないといけなくなる。
今のところ FONT_NAMES 構造体はオレオレ構造体であり、これは避けるべきだ。
逆に、UNICODE_STRINGはおそらく全体で使われている構造体なので、
(β)の関数を用意したら他でも色々使い回せ、全体的に記述を減らせるはず。
だから、次の手としては(β)+(γ)で記述を減らすべきだ。
ただし、もし仮に、型 OUTLINETEXTMETRICW を使うときには常に FONT_NAMES 構造体を知っているべきだ、というのであれば、
(α)の方向に変更していった方が最終的には綺麗になる。
てゆーか、頭大文字でも変数なのかー、まあそういうルールならそれでいいが。 >>143訂正
× .
○ ->
見れば分かるとは思うが。 OUTLINETEXTMETRICS構造体はwin32apiで定義済みだから、我々はそれを変更できない。 >>145
ご苦労様。
俺は書き換え後の方が断然いいと思うが、
3回+αだったから意外に効果が少なく、評価は分かれるかもね。
なにげに「書けばいいだろ」方式のべた書きって記述量は意外に少なかったりするし、今回もそうだった。
>>146
なるほど。
なら俺なら(β)+(γ)で主関数(IntGetOutlineTextMetrics)を4行減らす。
ただし、実際はサブ関数(γ)で4行増えるので、全体の記述量は変わらず、これも評価が分かれるかも。
UNICODE_STRING構造体も、変更があり得ないほどなら、ベタに密結合させて書いても問題ないからね。 >>140
すまん、見落としてた。
その場合、普通は4つのIntGetFontLocalizedNameもIntInitFontNamesに突っ込む。
というかな、一応オブジェクト指向を意識して書いた方が分かりやすいと思う。
FONT_NAMES構造体に纏める=そのメンバはほぼ常にセットで使う、という意味なのだから、
初期化も常にセットで行うべきなんだよ。それがコンストラクタであって。
だから、構造体に纏めた時点で、出来る限り「構造体」でアクセスすべきであって、
構造体の「メンバ」をいちいち無駄に参照するべきではない。
その場合、
IntInitFontNames(&FontNames, SharedFace, gusLanguageID);
としてしまって、メンバ全部を一度に初期化してしまうべき。
void FASTCALL IntInitFontNames(FONT_NAMES *Names, PSHARED_FACE SharedFace, ... ) // てか gusLanguageIDってどこからくるんだオイ?
{
RtlInitUnicodeString(&Names->FamilyNameW, NULL);
IntGetFontLocalizedName(&Names->FamilyNameW, SharedFace, TT_NAME_ID_FONT_FAMILY, gusLanguageID);
// その他*3
}
てな感じ。 で、さらについでに言うと、最後のコピーもメンバ関数として切り出して終わりだ。
つまり、FONT_NAMES構造体は、
void FASTCALL IntInitFontNames(FONT_NAMES *Names, PSHARED_FACE SharedFace, unkownType gusLanguageID); // コンストラクタ
void FASTCALL IntFreeFontNames(FONT_NAMES *Names); // デストラクタ
void FASTCALL SetFontNameAddresses(FONT_NAMES *Names, LPSTR **FamilyName, LPSTR **FaceName, LPSTR **StyleName, LPSTR ** FunnName); // コピー部分
の3つをメンバ関数として持ち、外部からは4つのメンバ変数
FamilyNameW, FaceNameW, StyleNameW, FullNameW;
をアクセスする必要が無くなるからprivateにしてしまえ、というのがオブジェクト指向になる。
結果、上位関数IntGetOutlineTextMetricsは、
IntInitFontNames(&FontNames, SharedFace, gusLanguageID);
で初期化して、駄目なら
IntFreeFontNames(FontNames);
を呼び、成功なら
SetFontNameAddresses(&FontNames, &Otm->FamilyName, &Otm->FaceName, &Otm->StyleName, &Otm->FunnName);
を呼んで終わる、と単純化される。
ところで、この書き方だと RtlInitUnicodeString と IntGetFontLocalizedName でも
alloc失敗で0が返ってきて落ちる様な気がするのだが、
これに対して対策されてないのは大丈夫なのか? >>149
自己レス。
RtlInitUnicodeString と IntGetFontLocalizedName については大丈夫っぽい。
IntGetFontLocalizedNameは元のL2408で
Status = STATUS_INSUFFICIENT_RESOURCES;
を食らう可能性があって、この場合は FONT_NAMES.FamilyNameW等は正しく初期化されない可能性がある。
プログラムフロー的にこれがないと言えればいいが、そうでない場合は
Needed += FontNames.FamilyNameW.Length + sizeof(WCHAR);
でいきなりLengthを見に行くのはそれなりに危険。
ただ、見たところ常に RtlFreeUnicodeString を先に呼んでて、
(これは無いから正確ではないが、)
名前やフローからするとおそらく Length = 0 してくれてるから多分大丈夫だね。
誰か探してくれば見ますが。 ■ このスレッドは過去ログ倉庫に格納されています