C言語標準ビルトイン型で関数オーバーロードしてはいけない

Posted on

可変長引数とva_listでオーバーロードしてみた

 Cの標準関数には、可変長引数を取るsprintfと、その引数をva_listで受け取るvsprintfの2つがあります

int snprintf(char *str, size_t size, const char *format, ...);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

 Cにはオーバーロードの仕組みがありませんから、引数の渡し方が違うだけの関数の名前が変わってしまうのもやむなしです。でも、C++ならオーバーロードをサポートしていますから、これらをこんな感じで一緒にできたら嬉しいな~と思いました。

std::string format(const std::string& format, ...);
std::string format(const std::string& format, va_list ap);

 が、しかし。このオーバーロードは64bit環境でのみ、*たまたま*うまく行きますが、32bit環境では失敗します。

 サンプルを使ってしらべましょう

#include <string>
#include <cstdarg>
#include <cstdio>


std::string format(const std::string& format, ...)
{
	printf("FORMAT 1 CALLED\n");
	return ""; /* サンプルなので未実装 */
}
std::string format(const std::string& format, va_list ap)
{
	printf("FORMAT 2 CALLED\n");
	return ""; /* サンプルなので未実装 */
}

int main(){
	va_list list;
	
	format("format: %s", "Hey!");

	return 0;
}

 さて、main内でのformat呼び出しに注目してください。第二引き数はconst char*なので、最初の可変長引数の関数が呼ばれるのを期待したいところです。

 実際、Fedora 16 x64(gcc 4.6.3)でビルドして実行すると

% ./test 
FORMAT 1 CALLED

 パチパチパチ。

 ところがしかし。Windows Vista32bitのMinGW(gcc 4.6.1)やUbuntu 11.04 32bit(gcc 4.5.2)で同じものをビルドすると…。

$ ./test.exe
FORMAT 2 CALLED

 後者が呼ばれてしまいます…な、なんで…?

ビルトイン型の実際の型を調べるには?

 オーバーロードがうまくいっている、という前提で考えて有り得そうなのは、va_listの実際の型が違う…のかもしれません。

 というわけで、調べてみましょう。g++のEオプションを用いると、プリプロセッサをすべて展開できます。

$ g++ -E test.cpp | grep typedef | grep va_list
typedef __builtin_va_list __gnuc_va_list;
typedef __gnuc_va_list va_list;

 むむむ、ヘッダファイルだけでは解決できませんね…。

 そこで、gdbの出番です。ptypeというコマンドを使うことで、実際の型を調べることができます。

 まずは、Fedora 16 64bitでやってみます。

% gdb test 
(gdb) break main
(gdb) run <- mainの一番上でストップします
(gdb) n <- 一行進めないと、スタック上にlistが定義されない
FORMAT 1 CALLED
22		return 0;
(gdb) ptype list <- main関数で定義されているva_list list;の定義を調べる
type = struct typedef __va_list_tag __va_list_tag {
    unsigned int gp_offset;
    unsigned int fp_offset;
    void *overflow_arg_area;
    void *reg_save_area;
} [1]

 というわけで、何かよくわかりませんが、va_listは構造体らしいです。なるほど、それならちゃんと可変長引数の方が呼ばれるのは納得です。

 64bit環境では構造体なら、32bit Windowsだと…。

(gdb) ptype list
type = char *

 期待通り!va_listの本当の型はchar*でした。だからオーバーロードでchar*を渡したらこっちが呼ばれてしまったんですね。

 これらの違いはなぜ起きるのでしょうか?

 おそらく、64ビットと32ビットでの呼出規約の違いに依るのだと思います。32bitのgccでは、引数をすべてスタック上(=アドレスがある)に格納されるため、va_listは単純なchar*で良いのですが、64bitのGCCでは基本的にレジスタ上に置くため、上記のように構造体で管理しているのでしょう。

 こういった事がありますから、型が明確でない場合はオーバーロードに使うのはやめたほうが良いですね…。

Visual Studio 2010だとどうなんのさ?

 同じリポジトリ内に入れておきました。私は32bit版windowsしかないので、そちらで試したところ、

20110325.png

 …どうやら、同じみたいです…。64bit版は知らないので、誰か試したら教えてね。


達人プログラマー:10年前の最先端と今の普通

Posted on

 一部では有名?な「達人プログラマ」を、大学の図書館で見つけたので読んでみました。

 職業プログラマ向けの啓蒙書、と言ったところ。純粋なプログラミングの話だけでなく、お客さんや同僚と一緒に仕事をしていく事に関してや、(私の好きではない)人生論っぽいところもあります。

 で、ぶっちゃけなのですが、2012年となった今、特に読まなくて良いと思います

10年前の最先端は、今の普通になった

 読んでもしょうもない、くだらない内容が書いてあるというわけではなくて、もはや今すでに十分浸透していて、インターネットで当然のように言われてる内容が多いからです。

 「ソフトウェアの直行性」、つまり「ソフトウェアの一部は他の部分と独立してるべき」というのは本当によく言われてることですし、「DRY(Don’t Repeat Yourself)」も、Ruby on Railsという有名かつ優秀なサンプルがあります。本で自然言語で読まなくても、コードを書きながらDRYってどういう事なのか、理解してる方は多いのでは。「テスト駆動開発」もどこかで聞かれた事があるでしょう。

 奥付をめくってみると、2000年11月の出版でした。ITバブルとか、あの頃です。20世紀です。もう11年以上経っています。当時はきっと最先端だったのでしょうが、今は十分に浸透した考えばかりです。11年前の本でわざわざ読むより、インターネットで検索して、実例をたくさん見たり、いろいろな人の意見を検索して調べていった方が、きっと新鮮で刺激的で、何より「Pragmatic†1」な情報が手に入ると思います。

 エディタの紹介も、Brief(DOSのエディタ!)だったり、ソースコード管理システムの例がCVSやらSCCSだったり…2000年はSVNがやっと出来た頃(!)なので、すごーく隔世を感じます。ITバブルでどっと混むでIT革命†2な、あの頃へ戻るタイムマシンに乗るような楽しさは有るかも知れませんね。

10年前は最先端だったけど、今でもそんなに…

 表明プログラミングEiffleSatherアスペクト指向プログラミングは未だにメジャーでは無いですね…。このあたりは読んでいて面白かったです。

 あと「どこでもいつでも何でも自動化」というコンセプトは、理解はされてても、まだそこまでメジャーじゃないきがします(私の周りだけかな?)。確かに自分でスクリプトを書いて自動化すると楽なのですけど、最初書くのが結構おっくうなんですよね…。もっと楽に自動化できるようなツール出来ないかなー!

  • †1: 原題:Pragmatic Programmer
  • †2: 本当に聞かなくなったワードばっかり!