char から int へのキャスト時の問題

コメント - CLX C++ Libraries の動作確認 への対応.コメントで,以下のソースコードでうまく encode/decode されないと言う指摘がありました(ソースコードは若干改変).

#include <iostream>
#include <string>
#include "clx/uri.h"

int main() {
    std::string dest = clx::uri::encode("http://日本語.com");
    std::cout << "encode: " << dest << std::endl;
    std::cout << "decode: " << clx::uri::decode(dest) << std::endl;
    
    return 0;
}

これを手元の環境(cygwin gcc 4.1.2, VC++ 8.0)で実行すると,以下のような結果が得られました.

encode: http://%ffffff93%fffffffa%ffffff96%7b%ffffff8c%ffffffea.com
decode: http://ÿffff93ÿfffffaÿffff96{ÿffff8cÿffffea.com

所謂,符号拡張 に関連したバグのようです.ある文字の文字コードを出力する場合('A' ではなく '65' と出力する) iostream (多くの場合は stringstream)を利用することが多いのですが,<< 演算子にそのまま char 型の値を渡すと文字として出力されてしまうため,いったん int にキャストしてから << 演算子へ渡します.この際,元の文字が ASCII 文字コードに収まらない*1 ような値のときに int へキャストすると,符号拡張のため上位の領域がすべて 1 で埋められてしまうようです.

同様の問題は CLX C++ Libraries - hexdump でも発生していたのですが,こちらの方は未修正でした.対策としては,0xFF (キャスト元が char の場合)でマスクした後に出力すると言う方法を用いています.

蛇足

修正したコードを試していたところ,VC++ の方でコンパイルエラーが発生していました.どうやら,マスク用の値を作成するために numeric_limit<int>::max() を使用していたのが問題だったようです.

VCだと(少なくともVC8では)、std::numeric_limits<Type>::max() がマクロに潰されます。 #define max(a, b)

http://blogs.wankuma.com/ddnp/archive/2007/10/16/102248.aspx

対策としては,上記の記事にある通り VC++ の(_MSC_VER が定義されてある)場合,NOMINMAX を定義すると言う方法を取っています.

*1:0x7F よりも大きな値.例えば,2バイト文字など.