-Wconversion は,いかがでしょう?

ちょいと寄り道して,前回の補足.

鋭い.(汗

はい.-Wconversion ,使えます.記憶が正しければ GCC 2.95.x から. ただ,検出が割と難しいようで,3.4系でパッチが出て,立ち消えになったり. 当時の経緯はこのエントリが詳しいです.

また,GCC 4.3 以降では,挙動が微妙に変わったりしています. 変更内容の詳細は,GCC Wiki を.

GCC は,そんな事情なので,前回の記述

もしかしたら気の利いた処理系では警告を出してくれるかもしれないが.

は,完全に筆が滑っております. お詫びします…が,必ずしも訂正はしません.

今回の後半は,その辺りについて.

-Wconversion の頼りなさ

確かに -Wconversion オプションは,暗黙的なダウンキャストについて警告してくれる. なので,gcc/g++ を使ってコーディングする場合には,可能な限り付けておくべきだ.

しかし,API 設計という今回の話題では,-Wconversion は必ずしも頼りにはならない.

例示しよう.

1
2
3
4
5
6
7
8
int
main(void)
{
  int a = 65535;
  long b;

  b = a;
}

これを手元の XCode の gcc (Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn) Target: x86_64-apple-darwin13.1.0) でコンパイルする.

1
2
$ gcc -Wconversion test.c
$

warning は出ない.

Renesas製 H8/300 というマイコン向けのクロス gcc (gcc version 4.6.4 20130204 (prerelease) (GCC) )でコンパイルしてみる.

1
2
3
4
$ /pizza/bin/h8300-pizzafactory-elf-gcc -Wconversion test.c 
test.c: In function 'main':
test.c:4:3: warning: conversion to 'int' alters 'long int' constant value [-Wconversion]
$

今度は warning が出る.

この差異は,gcc のバージョン違いに因るものではない.

前回,C言語仕様の整数型は,決っているようで何も決まっていないことに言及した. char も int も short も long も,それぞれどんな範囲の整数を保持できるかは,実行対象のCPUの特性に合わせて,処理系(コンパイラ)が勝手に決めて良いのだ.

(…まあ完全にフリーダムかというとそうでもなくて,long 型より広い範囲の整数を格納できる short 型はダメとか,制約はある. 商業誌掲載原稿なら,このへんの細かいことも書く.けれども,本稿は日記なので割愛.ご興味ある方はGoogle先生へ.)

H8/300 という系列のCPUは int が 16ビットであるほうが都合が良いので,デフォルトではそのようになっている.つまり 32767 までしか格納できない変数に 65535 を代入しようとしている. 結果,警告が出る.

ちなみにH8/300 は動作クロック 25MHz という,今や貧弱なCPUだが,有志のご尽力により Linux が動く. Linux は,元が i386 アーキテクチャ向けだったこともあって, int が 32ビット以上あることを暗に期待しているフシがある. そんな理由…だけではないだろうが,int を 32 ビットとみなしてコンパイルするオプション -mint32 が用意されている.これを使ってコンパイルしてみる.

1
2
$ /pizza/bin/h8300-pizzafactory-elf-gcc -Wconversion -mint32 test.c 
$

XCode の環境と同じく,警告は出なくなる. 暗黙のダウンキャストが起きなくなるのだから,当然ではある.

API 設計者は,-Wconversion には頼れない

つまり,-Wconversion は,今まさにコンパイルしようとしている環境において,暗黙のダウンキャストを検知する. 他の処理系で何が起こるかは関知しない . H8/300 の例から判るとおり,同じコンパイラの同じターゲットでも,ABI (アプリケーション・バイナリ・インタフェース)が違えば,検出したりしなかったりする.

このような動作では,API の移植性を担保できない.

アプリケーションの開発者にとっては,-Wconversion で少なからずのミスは見つかるかもしれない. ただし,C 言語 API の設計者は,特に移植性を問われる種類の API の設計者にとって,-Wconversion は,頼りない.

もう少し踏み込んで言ってもよい,頼ってはいけない.

蛇足だが,この点について,コンパイラの開発者を責めるのはお門違いだろうと,私は思う. 責めるとしたら,整数型についてほぼフリーダムとした,最初期にC言語の仕様を決定した面々だろう.

API 設計者が取りうる態度は,2つある

よく言えばターゲットアーキテクチャに対して柔軟,悪く言えば何も決めてない優柔不断な,C言語の整数型. これに直面する際に, API 設計者が取りうるアプローチは 2つある.

完璧な移植性は諦める派

一つは,動作環境に期待する整数型を明示してしまうというアプローチ.

char は 8ビット符号付き・int は 32ビット・short は16ビット・long は32ビット・ポインタは32ビット.それ以外は認めない.以上

これは,20世紀のUnix世界ではしばしば見られる態度だった. C言語といえばUnix.そのUnix動作マシンが概ね備える整数型を前提にすれば,世の中のほとんどをカバーできるではないか,と.

そのため,int やポインタが 16ビットであることが自然である MS-DOS の世界には移植できないソフトが山盛りになったりした. まあでも,MS-DOS なんて,俺らが使う Unix からみればオモチャだし,みたいな.

意地でも移植性を追い求める派

永らくC言語といえばUnixだったが,ゲーム機や組込みシステム開発に使われるようになるにつれ,話が若干違ってくる.

C99準拠って言っているのだから,それを達成するのがエンジニアとしての誠意ってモンだろう? DSPからスパコンまでで同じソースコードが動かなきゃ認めない.以上

冷蔵庫サイズのワークステーションよりも,ゲーム機やケータイのほうが出荷台数が多いのは,いくら世間に疎くても判る話だ. Unix も,ゲーム機やケータイの高機能化に助けられて,それらの中に入れるようにはなった. しかし,同時に組込みシステムの広がりも爆発的なものがある. いまやクルマはコンピュータなしには動かない.エンジンの環境性能を満たせない. ゲーム機本体がUnixでも,周辺機器を動かしているマイコンには Unix は入らない.

このような,非Unixな世界では,整数型に前提を設けることなんてできない. 移り変わりの早いマイコンの世界で,特定のアーキテクチャに依存するコードなんて,リスクが高すぎて書けない. ライブラリも同様に,アーキテクチャ独立が求められる.

C言語 API 設計者の異文化が交わる場所としてのmruby

実際には,このような2つの派閥の極端に立つ人は,まず居ないだろう. これがトレードオフの問題だということに気づけない人は,ソフト屋には向かない. だが,両極端の間のどこに重心を置くかというのは,人により,またはプロジェクトにより,分かれる.

mruby というのは,立ち位置として面白いと,個人的には思っている.

mruby が処理する Ruby 言語は,Unix 文化の代表的側面である Little Language がその根底にあるように思える. また,LAMP から Rails へという,Unix 系文化の進化に添って大きくなってきた. だから,その API 設計が,理屈は判っていながらも Unix 的な方向に時々流れるのは,不思議なことではない. 私の好みはさておき.

同時に,mruby は組込み向け言語処理系でもある. 利用者の少なからずは,移植性が担保されていないなら魅力を感じない. API は,C99に準拠する限りのあらゆる処理系であっても耐えうる設計か. そのことを常に問われる分野の人も,コードを観ている.

mrubyでのMatz氏のAPI変更に対して,私が文句を言っているときは,APIに対する2つの派閥を思い浮かべると,解りやすいかなと思う.

a bit philosophical とか言われて,まあそうだよなと心の何処かで思いつつも,あるんだよ.広大な組込み世界には.びっくりするような構成のアーキテクチャってのが.

ちょいと脇道に逸れたけれど,@amasawa_seiji さんから mention 頂かなかったら書きそびれたはずの話題に言及できた. あと1回…2回…くらい? あんまり長くても飽きるので.私が.