2012/06/05
コーディングスタイル
世の中にはプログラマの数だけコーディングスタイルがあると思うけど、
Linux カーネルのコーディング規約 と対比しながら、自分のコーディングスタイルを晒してみる。
勿論これはスクラッチで自分が自由に作成出来る場合のスタイルであって、
プロジェクトなどによりコーディング規約が定まっている場合や
他人が作成したソースを修正する場合は基本的にはそのスタイルに合わせる。
- インデント
-
linux コーディング規約とは完全に意見を異にするが、
基本的にタブは 4 文字にしている。
通常の 80 カラムのディスプレイで見たときに タブが 8 文字だと間延びして見えてしまう事と、 ネストが 3 段以上になると格段に見づらくなる事が主な理由。
勿論、「3 段以上のネストは悪い」
という意見は 原則的に賛成だが、 そうは言っても実際はもっと深くネストする事が 避けられない場面も多々ある。 - case 文
-
linux コーディング規約とは若干異なっていて、
switch と case はインデントさせている。
深い理由はないのだが何となく見やすいと思う。swich(suffix){ case 'G': case 'g': mem <<= 30; break; case 'M': case 'm': mem <<= 20; break; case 'K': case 'k': mem <<= 10; /* fall through */ default: break; }
- 1行に複数の文を書かない
-
これは linux コーディング規約に賛成。
特に if に続くセンテンスは必ず改行して書いて欲しい所だ。if(condition) do_this; do_something_everytime;
- インデントはタブを利用する
-
これも linux コーディング規約に賛成。
- まともなエディタを利用する
- 何を「まとも」とするかで宗教戦争が勃発しちゃいそうだが、 例えば 1 行を 80 カラム以上で横にスクロールさせながら表示する エディタは使って欲しくないと思う。 そういうエディタを使っていると往々にして 1 行が 80 カラム以上の見づらいソースになりがちだから。
- 行の長さは 80 カラムが限界で強く推奨される
-
これも linux コーディング規約におおむね賛成。
ただし分割した 2 行目以降を「かなり短く、かなり右におく」
事はしない。
基本的には 1 行目の先頭からタブ 1 個から 2 個分 右にインデントした位置にしている。if(condition) printf(KERN_WARNING "Warning this is a log printk with " "3 parameters a: %u b: %u c: %u\n", a, b, c); else next_statement;
- ブレースの位置
-
これも linux コーディング規約におおむね賛成。
if、switch、for、while、 do においてはブロックの開始行の行末に開きブレース、 ブロックの最終行の行頭に閉じブレースというスタイルで書いている。 関数定義のブレースについての書き方も同様に賛成で、 開きブレースは次の行の行頭に書いている。
ただし else 節を開始する開きブレースは 昔から次の行の行頭に置くクセがあるので今でもそのまま書いている。/* 関数定義 */ int function(int i) { body; } /* for 文 */ for(i=0; i<max; i++){ do_condition_1; do_condition_2; } /* while 文 */ while(condition){ do_condition_1; do_condition_2; } /* do ~ while 文 */ do{ do_condition_1; do_condition_2; }while(condition); /* if ~ then ~ else 文 */ if(condition == 1){ do_condition_1_1; do_condition_1_2: } else if(condition == 2){ do_condition_2_1; do_condition_2_2: } else{ do_condition_other_1; do_condition_other_2: }
- 必要のないブレースを使わない
-
これも linux コーディング規約に賛成。
特に if の後ろには単文でも必ずブレースを使う人がいるが、 ソースが煩雑になるので不要なブレースは省略している。if(condition) this(); else otherwise();
長くなってしまったので今回はここまで。
(多分)続く…
2012/05/26
gdb の意外に便利な使い方
開発環境には必ずインストールされている gdb (1) コマンド。
本来の機能はプログラムのシンボリックデバッガなのだが、
若干意外とも言える便利な使い方がある。
それはプログラマなら重宝する 16 進数電卓としての用法。
$ gdb -q (gdb) p/x 10 ← 10 を 16進数で表示 $1 = 0xa (gdb) p/x 20 ← 20 を 16進数で表示 $2 = 0x14 (gdb) p/x $1 + $2 ← 10 + 20 を 16進数で表示 $3 = 0x1e (gdb) p $2 - $1 / 3 ← 20 - (10 / 3) を 10進数で表示 $4 = 17 (gdb) p ($2 - $1) / 3 ← (20 - 10) / 3 を 10進数で表示 $5 = 3 (gdb) q例にある様に一度利用した値は変数 $n に保存されるので、 計算結果を後から参照するのも便利だし、 四則演算の優先順位もちゃんと解決してくれるので電卓としても結構使える。
ちなみに入力データは Unix/C の原則に従って、 0 が先行する値は 8 進数、 0x が先行する値は 16 進数として認識される。
ちょっとした便利機能の紹介でした。
2012/04/02
ご近所さん
我が家の近所には同じ年頃の子供を持つ家庭が偶然多く暮らしているので、 非常に楽しく素敵なご近所つきあいをさせて頂いている。
ご近所さんのうちの1家族は震災の随分前に福島から引っ越してきたのだが、 同じ年の子供がいたのでとっても仲良くさせて頂いていた。 いつも仲が良いとっても素敵な家族で子供達も凄く素直で優しい良い子達だった。 その家族が仕事の都合で先日引っ越してしまったのだが、 引っ越した先が京都だという事を訊いて何とも言えない気分になった。
横浜を経由しているとは言え福島から来た家族がいじめられたりしないだろうか? 今思うとその家族が乗っている自動車のナンバーは福島ナンバーだったんだけど、 特に子供達がいじめや仲間はずれになっていないだろうか。 がれき受け入れの記事を読んで人ごとながら胸が痛くなる思いで一杯だ。
京都に限らず福島からの人々が虐められ迫害されているという記事や
ニュースを見る度に本当に心が痛くなる。
彼ら・彼女らこそが一番の被害者なのに、
一番傷付き疲れ果て様々な迷惑や不便を味わっているのに、
何故そんな仕打ちを受けなければならないのだろうか。
「自分たちの子供のため」などと言って思考停止していないで、
足りないなら足りないなりにその頭で考えて欲しい。
本当に自分の子供の事を考えているのであれば、
親として自分の子供に見せるべきはどういう姿なのか、を。
よく言われているが子供は親の背中を見て育つのだ。
理由も根拠もなく他者を差別し迫害し虐げる親のその姿をね。
2012/02/22
C 中級者が意外と陥りやすいワナ
ある程度プログラミングにも慣れてきて
言語仕様もそこそこ理解した中級者にとって
意外と陥りやすくセキュリティホールの温床になりがちなワナ。
勿論オンラインマニュアルにも仕様として明記してあるのだが、
直感とは反する仕様のなので特に中級者にこれらのミスが多い気がする。
- strncpy(3) は ``\0'' 終端してくれない場合がある
- strncat(3) のサイズ指定はコピー先のサイズを指定するのではない
- snprintf(3) は領域が重なってはいけない
strcpy(3) はバッファオーバーフローの危険性があるから
strncpy(3) を利用する様によく言われるが、
ここにワナが潜んでいる。
strncpy(dst, src, len);
とした時に
文字列 src の長さが len バイト未満の場合は
dst は ``\0'' で終端される。
終端どころか dst の残り領域は何故か
全て ``\0'' が詰められるという
無駄とも思われる謎仕様。
ところが文字列 src の長さが len バイト以上の場合、
dst に len バイトだけコピーするという
memmove(3) 同様の動作となってしまい、
dst が ``\0'' で終端されない。
文字列操作の関数なのにも関わらず得られた結果が
文字列として扱えないという不思議な仕様なのだ。
snprintf(3) などの様に
len - 1 バイトを dst にコピーして
``\0'' で終端される事を期待していると
痛い目に遭ってしまう。
文字列操作関数なので、
殆んどの場合 ``\0'' 終端が必要になると思うので
以下の様な処理が代替になる。
strncpy(dst, src, sizeof(dst) - 1); *(dst + sizeof(dst) - 1) = '\0';
strcat(3) も同様にバッファオーバーフローの危険性があるので
strncat(3) を利用する様によく言われるが、
ここにもワナが潜んでいる。
strncat(dst, src, len);
とした時の len はコピーする src の長さであって、
前述の strncpy(3) や snprintf(3)の様に
dst のサイズ指定ではない
ここで dst のサイズのつもりで len を指定すると
バッファオーバーフローの原因になってしまう。
殆んどの場合は dst の最大値が重要になると思うので
以下の処理が代替になる。
strncat(dst, src, sizeof(dst) - strlen(src) - 1);
前述した通りに使いやすいとは言いづらい strncpy(3) や
strncat(3) の代用として、
コピー先のサイズ指定が直感的な
snprintf(3) を利用する場合も多いと思うが、
strncat(dst, src, len) の置き換えとして
snprintf(dst, sizeof(dst), "%s%s", dst, src);
とすると正しく動作しない(結果は処理系依存になる)。
ANSI X3.159-1989 (ANSI C) や ISO/IEC 9899:1999 (ISO C99) 、
IEEE Std 1003.1-1988 (POSIX.1) 等をざっと探したけど
関連する記述は見つける事はできなかったが、
snprintf(3) は重なった領域では正しく動作しない様だ。
2012/02/10
2012/01/11
OWC SSD for MacBook Air
MacBook Air Late 2010 のディスク容量が逼迫してきていたので、
折からの円高に後押しされ(た事にして)
OWC の Mercury Aura Pro Express を購入した。
MacBook Air はその薄さ故か、
通常の HDD タイプの SSD ではなく
独自な専用設計の SSD モジュールを使用している。
そのため汎用的な HDD タイプの SSD は利用できず、
Apple からは交換用の SSD モジュールは発売されていないのだが、
OWC では互換性のある交換用の SSD モジュールを販売しているので、
それらの SSD モジュールのうち 240GB のものを購入した。
国内でも OWC の SSD モジュールを扱っている販売店はあるのだろうが、
折から円高でもあるので OWC のサイトから普通に購入する。
取り外した純正の SSD モジュールを外付けディスクとして流用するための
インタフェイスボックスを同時購入すると多少割引になるとの事なので、
折角なのでそれも同時に購入する事にする。
大体 1 ドル 88 円弱の換算となったので
送料を払った上でも国内で購入するよりも随分と格安にて購入できた。
交換自体は非常に簡単なので困ったり迷ったりする事もなく順調に作業完了。
OWC の Web Site でも
機種毎の交換作業の動画 が公開されていので
一通り閲覧しておけば作業の手順もつかめるので良いだろう。
- TimeMachineを利用したバックアップ
- 今回は USB 接続の外付け HDD に TimeMachine を利用して バックアップを取得した。
- 裏蓋の取外し
-
MacBook Air の裏蓋はペンタローブという
特殊な形式のネジが使用されているが、
SSD に同梱されている専用のドライバを利用すれば簡単に
ネジをはずす事ができる。
裏蓋を外した状態 - SSD モジュールの取外し
-
トルックスネジで固定されているオリジナルの SSD モジュールを
これも同梱されてる専用のドライバを利用してゆるめると、
オリジナルの SSD は簡単にはずれる。
オリジナルの SSD モジュール - SSD モジュールの組み付け
-
OWC の SSD モジュールをオリジナル同様に差し込んで、
トルックスネジを締め付けて固定すれば完了。
OWC の SSD モジュール - 裏蓋の組み付け
- 取り外した裏蓋を組み付ける。 ネジは仮締めしておき、最後に対角線上のネジを 交互に本締めするのがコツ(というか基本ですね)。
- 環境の復元
- 環境をバックアップした外付け HDD を接続した状態で MacBook Air に標準添付される USB メモリから起動。 自動で実行されるインストーラのバックアップから復元 を選択して新しい SSD に環境をレストアして作業完了。
環境の復元まで全て終了したら MacBook Air を起動すると、
無事に新しい SSD への移行が完了。
システムプロファイラ の シリアル ATA の項目を確認すると
OWC Merucry Aura Pro Express SSD と表示されている。
但し、現在の Mac OS X (10.6.8) では社外品の SSD に対する
trim 機能は無効とされてしまっているので、
システムプロファイラ の TRIM サポート項目は
いいえと表示されている。
この状態でも勿論利用可能なのだが、
折角高速な SSD の機能を最大限に使うために
Trim Enabler
を利用して trim 機能を有効にして再起動する。
再度 システムプロファイラ 上で確認すると、
今度は TRIM サポートが はいと表示されるので、
SSD の性能が最大限に活用できる気がして嬉しい。
2012/01/04
「今度ね…」
子供を育てていると、ついつい「今度ね」とか「後でね」とか言ってしまう。
大人にとってはその場しのぎで、次の瞬間には忘れてしまう様な事でも、
子供は「今度」や「後で」を楽しみに心待ちにしている事が多い。
そして結局「今度」や「後で」が来ない事を知ると落胆してしまう。
約束を守って貰えない親だと子供に思わせれるのは親として情けないと思うし、
約束を守って貰えない親だと思うしかない子供は不憫だと思う。
だから、自分の子供に「今度」や「後で」の約束をしたら、 必ずこちらから「今度」や「後で」を実現させてあげる様にしようと思うし、 なるべく「○○できたら」とか「○○の後で」と条件を明確にする様にしている。
極論かもしれないが、そういう細かな不信感の積み重ねで 大人になった時に約束は守らなくても良いと思う様な 責任感が希薄な人に育っていくのだとさえ思う。だから軽々しい口約束は極力控えて約束したらちゃんと守るようにしないとと思う。
2012/01/01
2011/12/14
cvs や svn のリビジョン番号
プログラムを作成する際、
特にまだ未完成で頻繁に修正とコンパイルを繰り返している間は、
どのリビジョンのプログラムを実行しているのか知りたい事はよく有る。
そんな時に、例えば実行時オプションとして -v を指定すると
cvs や svn のリビジョン番号とコミットした日時が表示されると便利だ。
しかし、多くの場合において -v オプションを解析し実行するだろう
main 関数が含まれるファイルが最終修正されているとは限らない。
従って main 関数の含まれるファイル中に
static char *id = "$Id$";などと定義しておいて commit 時に $Id$ を展開したとしても、 必ずしも最新のリビジョンにはならない場合が多い。
そこで -v オプションのハンドラ部分では
static char *id = "revision: " REVISION ", " COMMITED ;と記述としておいて make 時に自動で REVISION と COMMITED を全てのソース/ヘッダファイル中の cvs や svn の $Id$ キーワードから取得して設定する方法を検討してみる。
build 時に必ず実行する必要がある処理なので Makefile に記述して、 make 中で自動的に実行してしまうのが楽。
: # 変数定義 PROGRAM = myprog OBJECTS = sub.o pthread.o common.o util.o HEADER = myprog.h const.h config.h # 全ソース/ヘッダ中の `$Id' 行から最大のリビジョンを取得する REVISION = $(shell awk '/\$$Id/{ if($$4 ~ /[[:digit:]]/) \ if(max < $$4) max = $$4 } END{ print max }' \ $(OBJECT:.o=.c) $(HEADER) $(PROGRAM).c) # 全ソース/ヘッダ中の `$Id' 行から最大のリビジョンのコミット日時を取得する COMMITED = $(shell awk '/\$$Id.*'$(REVISION)'/{ \ print $$5 " " $$6; exit }' \ $(OBJECT:.o=.c) $(HEADER) $(PROGRAM).c) # コンパイルオプション(REVISIONとCOMMITEDを定義) CFLAGS = -g -Wall -Wextra -Werror -Wno-unused-parameter -I.-O2 \ -DREVISION=\""$(REVISION)"\" -DCOMMITED=\""$(COMMITED)"\" : : # ターゲット(デフォルトターゲットは touch と $(PROGRAM)に依存 all: touch $(PROGRAM) # $(PROGRAM).o ファイルの削除($(PROGRAM).o ファイルは必ずコンパイルする) touch: rm $(@).o $(PROGRAM): $(OBJECTS) $(CC) $(CFLAGS) -o $(@) $^ $(OBJECTS): $(HEADER) :これでどのファイルを commit した場合でも 必ず最新のリビジョンとコミット日付が -v で表示される。
当初ターゲット touch は touch $(PROGRAM).c としていたが、
とある方よりの指摘で rm $(PROGRAM).o に変更した。
ご指摘感謝です > N.S. さま
2011/11/26
shell での while ループにおける問題
シェルスクリプトでコマンドの出力をループ処理する場合は
パイプ | を使用するのが一般的な手法だと思うが、
パイプ | を使うとループが別なプロセスとして実行されるので
ループ内部で設定したシェル変数がループ外部で参照できないという問題があり、
(存在するとすれば) shell script 業界では割と FAQ 的な問題だ。
何が問題かと言って posix で定義されていないから
シェルの実装に依存しているのが一番の問題だったりするのだろう。
この問題に対しては、シェルビルトインの exec (1) を利用して
ディスクリプタを複製して while ループの入力を標準入力にしてしまうのが
一番汎用的で柔軟性のある対応だと思うのだが、
シェルを愛する面々は
皆同じ様な苦労をしているのだ
と改めて認識した。
当初は一時ファイルを作成する方法を考えついたのだが、
自分的にはなるべく一時ファイルを使いたくなかったので
ファイルから入力する部分をバッククォートによるコマンド実行に置き換え等、
色々と試行錯誤した結果ヒアドキュメントを使う方法に落ち着いた。
# これでは正常に動作しなかった exec 3<&0 0<`${command}` while read line :
勿論一時ファイルを作成しても良いのだが、 ヒアドキュメントを利用する事で一時ファイルが不要になる分だけ 処理的に美しいかなと思う (完全に好みの問題)。
1#!/bin/sh 2# コマンドの出力をバッククォートしてヒアドキュメントとして使用する 3 4command="/path/to/command" 5count=0 6 7# exec 3<&0: 標準入力 (FD0) を FD3 に複製 8# exec 0<<EOF: ヒアドキュメントを標準入力として使用 9exec 3<&0 0<<EOF 10`${command}` 11EOF 12 13while read line 14do 15 : 16 count=`expr ${count} + 1` 17done 18 19# exec 0<&3: FD3 に複製した標準入力 (FD0) を復帰 20# exec 3<&-: FD3 をクローズ</i> 21exec 0<&3 3<&- 22 23echo ${count}
もう一点は子プロセスで親プロセスの変数値を変更する方法。
こちらはプロセスが別になってしまうとファイルを使うしかないので、
同じプロセス内で関数呼び出しとして対応する方法が汎用的ではないだろうか。
1#!/bin/sh 2# eval を利用して呼び元の変数を設定する 3 4default="デフォルト値" 5 6sub() 7{ 8 9 local _val 10 11 : 12 _val="設定したい値" 13 14 eval "${1}='${_val:-${default}}'" 15 16} 17 18value="元の値" 19 20sub value 21 22echo ${value}
ちなみに上のリンク元にある parent.sh の中の処理で
${tmp_file} の内容を for ループで eval しているが、
これはシェルビルトインの . もしくは source を利用して
. ${tmp_file} としても同一の結果が得られるので楽だと思う。
1#!/bin/sh 2 3g1="aa" 4g2="bb" 5 6tmp_file=`mktemp /tmp/ps.XXXXXX` 7export tmp_file 8 9./child.sh # child.sh で、上で設定した変数 g1, g2 の値を変更する 10 11# for v in `cat $tmp_file`; do 12# eval $v 13# done 14# ${tmp_file} の内容は name='value' 形式が保証されているので 15# ファイル自体を `.' コマンドで読み込むだけで 16# 行毎に eval を実行しなくてもシェル変数に代入された値が利用できる 17 18if [ -f ${tmp_file} ] 19then 20 . ${fmp_file} 21 rm -f ${tmp_file} 22fi 23 24echo $g1 #=> cc に変更された 25echo $g2 #=> dd に変更されたブログに直接コメントしたかったのだが、 はてなのアカウントが無いので申し訳ないがここで意見させて頂く。