syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

劇場版ゼータガンダムこそ抹消すべき最悪の黒歴史

Tokyo MXGガンダム再放送でもCMが流れていますが、宇宙世紀ガンダムシリーズのBDが廉価版として再販されることになりました。4Kリマスターではない従来版の再販ということなので、画質は劣るようですが、正直4Kでなくとも必要十分なんじゃないでしょうか。それよりも大して価格が下がってないことのほうがどうかと思います。

それはそうと、ラインナップにあの悪名高きクソ劇場版ゼータが入っていることが気になります。あれは宇宙世紀ではありません。別の世界線黒歴史です。CMであのクソゼータの映像が流れるたびにゲロを吐きそうになります。AGEのように駄作とかいう生易しいレベルではなく、TV版ゼータどころか、我々が愛した宇宙世紀ガンダムシリーズそのものに対する完全な冒涜です。たまに「賛否両論」とか書かれていることがありますが、日本語が読み書きできないのでしょうか。最後まで観ても賞賛できる部分は何一つありません

自分は宇宙世紀シリーズの作品は概ね観ていて、TV版ファースト、劇場版ファースト、ダブルゼータ逆襲のシャアF91Vガンダム、そして富野由悠季監督作品ではない0080、0083、08小隊も好きなんですが、1991年のローカル再放送で「最初に観たガンダム」として、TV版の「機動戦士Ζガンダム」にはとりわけ思い入れが深かったのです。高校生のときにビデオレンタルで最初に見返したのもゼータでした。ガンプラも旧1/144キット、旧HG 1/144(ウェイブシューター)のゼータを組んだことがあります*1。シャープな印象のゼータはもちろん、マッシブなガンダムMk-IIも好きです。

ではなぜ吐き気を催すほどクソ劇場版ゼータが嫌いなのか。ネタバレ前提なので要注意ですが、TV版より先にクソ劇場版を観てしまうことでゼータガンダムが何たるかを誤解してしまう人、TV版を視聴済みでうっかりクソ劇場版を観てしまって死ぬほどガッカリする人が一人でも減ることを願い、ここにそのムカつく点を列挙していきます。

劇場版ゼータが大嫌いな理由その1・「永遠のフォウ」のカット

ゼータガンダムのストーリーは、スペースコロニー、月、地球などを舞台に、主人公カミーユ・ビダンの属するエゥーゴ(反地球連邦組織)、敵対するティターンズ(地球連邦の特殊部隊)、第三勢力アクシズ(旧ジオン公国)の三つ巴による複雑な戦争を描いており、劇場版3本に収めるには尺が足りなすぎます。ですが、だからといって何でもかんでもカットしていいわけではない。

カミーユは作中で二度地球に降りているんですが、一度目にホンコン・シティで出会った薄幸の美少女、フォウ・ムラサメと恋に落ちます。ですがフォウはムラサメ研究所(地球連邦所属)の強化人間であり、無くした記憶を取り戻すためにサイコガンダムに乗り、カミーユと戦うことになります。

二度目に地球に降りたとき*2カミーユティターンズキリマンジャロ基地でフォウと再会するのですが、彼女はさらに強化・調整されてカミーユとの出会いすら忘れていました。戦闘の混乱のさなか、最終的に自分の心を取り戻したフォウは、カミーユをかばって戦死します。フォウはファーストガンダムで言うとララァ・スンのような存在でもあるのですが、ファーストのようにオカルトチックなニュータイプ同士の邂逅は描かれないものの、フォウの死はカミーユのその後に暗い影を落とすことになります。「水の星へ愛をこめて」のインストゥルメンタル・バージョンが流れる中、カミーユがフォウの亡骸を抱きながら、クワトロに「シャア・アズナブルに戻る」ことを促すシーンは極めて鮮烈な印象を残しました*3

しかしクソ劇場版ではそもそも地球に一度しか降りず、「永遠のフォウ」もなければ「ダカールの日」もありません。ストーリー上重要なターニングポイントや印象的なシーンがバッサリ削られているんです。フォウの死やダカール演説が明確に描かれないなんて、肉の入っていないすき焼きのようなものです。これを改悪と言わず、なんとするのか。「永遠のフォウ」の重要性に比べたら、アッシマーの活躍なんかどうでもいいんです。TV版には他にも「ハーフムーン・ラブ」「湖畔」といった、各登場人物像を掘り下げる印象的な回が沢山あるんですが、クソ劇場版ではことごとくカットされてしまっています。そもそもゼータを劇場版3本に収めようとする企画自体が無謀なんです。

劇場版ゼータが大嫌いな理由その2・結末の変更

自分があのクソ劇場版ゼータを嫌う最大の理由は、主人公・カミーユの精神崩壊というゼータの物語の終着点とも言える結末を安直に変更してしまったことです。富野監督とは到底思えない采配に、もはや富野は死んだ、と完全に落胆・失望しました*4*5
TV版では、カミーユ・ビダンは戦争のさなか、様々な人々との出会いと死別を体験していきますが、ニュータイプとして感性が強すぎるがゆえに極限まで精神的ストレスを溜め込んでいきます。最終回「宇宙(そら)を駆ける」で、憧れのエマ・シーン中尉の死に立ち会い、別れを告げるカミーユの表情は穏やかですが悲壮そのものです。ティターンズパプテマス・シロッコが駆るジ・オとの最終決戦では、死んでいった人々(カミーユが関わった女性たち)の力を借りてジ・オの動きを封じ、ウェイブライダーで突撃してシロッコを倒すものの、シロッコの怨念により精神を持っていかれます*6*7

戦闘終了後、母艦・アーガマ所属の数少ない生存者として、メタスのパイロットのファ・ユイリィカミーユの幼馴染)は、精神崩壊して自分が誰なのかも分からなくなったカミーユの様子に愕然としながらもアーガマに報告。また、戦死したエマ中尉の乗っていたガンダムMk-IIが漂流しているのを見つけて「お前もアーガマに帰りたいのね……」と語りかけます。最後に百式の残骸が流れていき、Fin.のメッセージ。BGMはやはり「水の星へ愛をこめて」。悲劇ではありますが、これ以上ない本当に完璧な結末でした。続編のダブルゼータでは精神崩壊したカミーユをファが献身的に介護します。

しかしクソ劇場版ではカミーユは精神崩壊しません。散々死人を出しまくった戦争の後なのに、ファと宇宙空間でいちゃついて終わりです。しかもそのいちゃつき方とセリフ回しが気持ち悪いのなんの。吐き気がします*8。何なんだこれは。悪夢のような三流のクソシナリオ。何より、カミーユが精神崩壊しないということは、ダブルゼータジュドー・アーシタへのバトンタッチ*9につながらなくなり、さらには間接的に逆襲のシャアにもつながらなくなります。クソ劇場版ゼータは葬り去るべき別の世界線であり、黒歴史以外の何物でもありません。他の作品をないがしろにしてしまうような作品など要りません。リセットでやり直しのきくゲームの別ルートのような何か*10に改竄されてしまったばかりか、続編としてガンダムサーガを紡いできた名作の数々に泥を塗りたくったクソ劇場版ゼータ。こんなゴミクズのような作品の、一体どこに魅力があるというのでしょうか。

テッカマンブレードも主人公が最終的にすべてを失なう過酷なストーリーなのですが、だからこそ記憶に残るんです。あれが単なるご都合主義のハッピーエンドだったら、後世に語り継がれる名作になりえたでしょうか。否です。

劇場版ゼータが大嫌いな理由その3・声優の変更

(以下、敬称略)
引退されてしまったファ(松岡ミユキ)は仕方ないにせよ、なぜかフォウ(島津冴子)、サラ(水谷優子)、ロザミア(藤井佳代子)といった重要な登場人物の声がことごとく変えられてしまいました。声優が音響監督に枕営業しているかどうかなんて知ったことではありませんが、そういう噂がたつのもやむなしと思えるくらい、疑念・疑惑の残る気持ち悪い交代劇でした。加えて「音響監督に裏切られた」とかいう醜い言い訳をする富野監督に対する印象は、この時点で最悪になりました。こいつらはクリエイター以前に人間としてどうなのか? カツ(難波圭一)やハヤト(鈴木清信)も、なぜ変えたし、という印象しかありません。決して後任の声優が悪いわけではありませんが、オリジナル声優が存命で現役なのに変える理由が分かりません。特にサラは声優ですらなく、棒演技のド素人(池脇千鶴)を使ったせいで、違和感ばかり気になって話の内容が頭にまったく入ってきませんでした。

ただ、劇場版ゼータは本当に黒歴史と断定できるクソ作品だったので、逆説的に考えるとオリジナル声優の方々の輝かしい経歴に汚点を残さずに済んだことは不幸中の幸いかもしれません。

劇場版ゼータが大嫌いな理由その4・何も印象に残らない主題歌

ゼータの主題歌は鮎川麻弥森口博子以外にありえません。どこの馬の骨ともしれない薄汚いチャラ男がコネで好き勝手に歌っていいものではない。恥を知りなさい。薄っぺらいクソ映画の主題歌としてはお似合いとも言えますが。

逆説的に考えるとオリジナル歌手の方々の(以下略)

劇場版ゼータが大嫌いな理由その5・CGベースのオナニー新作画

劇場版ではいくつかのシーンが新規作画されているんですが、資金の問題で基本的にTV版の旧フィルムを流用しているもんだから落差が激しくて違和感ありまくりです。また、新規シーンはCGベースで作画されているらしく、キレイだけどつまらない絵でした。ああいうのをクリエイターの自己満足と言います。より下品な言い方をするとオナニー(自慰行為)ですね*11
とはいえ、仮に潤沢な資金があって新作画で全編作り直したとしても、劇場版ゼータに対する評価は最悪のままで変わりなかったと思います。きっと「金をかけて壮大なゴミを作ったんだなあ……」という感想だけを抱いたことでしょう。アニメにおいて作画は確かに重要なファクターのひとつですが、肝心のストーリーがぐだぐだでは意味がありません。昨今のアニメは確かに昔に比べて作画密度は上がっているものの、お上品にまとまっているだけで、キャラクターデザインもストーリーも、どれも似たり寄ったりの薄くてつまらない作品が増えました。日本には迫力のあるメカを描けるアニメーターはもうほとんどいないんでしょう。面白かったと言えるロボアニメはダイミダラーが最後でした。

ちなみに劇場版ファーストでは安彦良和によって作画がいくつか手直しされていますが、TV版放送終了からほとんど時間をおいていないので違和感がないし、そもそも基本的なストーリーや結末自体はTV版から変わっておらず、重要なシーンもほとんどカットされていません。同じ「劇場版」でもえらい違いです。劇場版ファーストは公開から40年近く経つ今でもなおガンダム入門としてお勧めできる金字塔ですが、クソ劇場版ゼータは検討の対象にすらなりません。口にするのも汚らわしい。
ところで、なぜかファーストガンダムを今の映像技術でリメイクすることを事あるごとに希望する人がいるようですが、絵がキレイなだけでコレジャナイ感満載の残念な駄作に成り下がることは目に見えています。そもそも今の時代メカを描けるアニメーターが絶滅危惧種に指定されているどころか、セイラさんもブライトさんもミライさんもマ・クベもナレーターも亡くなってしまっているのに、オリジナルを超える感動をもたらすものが作れるわけがありません。ファーストもTV版ゼータも、オナニー技術や札束によって生まれた作品などではないのです。あの時代こそが作り上げた、代替の利かない唯一無二の珠玉の作品なのです。

総論

クソ劇場版ゼータをこれから観ようとしている人にひとこと言っておきます。

観ないでください(怒)
(声:シャクティ

*1:ゼータのウェイブライダー可変機構は複雑で、完全変形とプロポーションを両立するガンプラは当時の技術では不可能だったようです。MG以降はカトキ臭が強すぎて「ゼータガンダム」らしくないので買いませんでした。

*2:ウェイブライダー百式を助けて大気圏突入するシーンも印象的でした。続編のダブルゼータでもジュドーのゼータがエルピー・プルキュベレイMk-IIを救う形で大気圏突入するシーンがあり、演出上の要となっています。

*3:ゼータではファーストの主人公アムロ・レイと宿敵シャア(クワトロ・バジーナ)が共闘し、「永遠のフォウ」では二人がフォウの死に立ち会っていることも見逃せません。ゼータではアムロは宇宙には上がらず、地球でエゥーゴに協力する組織「カラバ」の一員として戦います。放送当時はアムロガンダムに乗らないことが批判されていたようですが、ゼータの主人公はカミーユとシャアであり、また結果的に逆襲のシャアでのニューガンダムの活躍の印象を強めることになったので、むしろゼータでは脇役に徹していてよかったのではないかと思います。

*4:各章のサブタイトルのダサさも富野のセンスの終焉を物語っています。

*5:Vガンダムに代表される「黒富野」に対して、いわゆる「白富野」が嫌いというわけではなく、例えばブレンパワードは好きな作品のひとつです。ターンエーもガンダムとして観ると微妙ですが、悪くはなかったと思います。

*6:カミーユの精神崩壊は酸素欠乏症が原因とかいう推測をする人もいますが、のちにダブルゼータのラストで回復しているらしき描写があることから、単なる酸素欠乏症ではないと思います。

*7:ちなみに小説版のゼータのラストでは、カミーユパイロットスーツのバイザーが宇宙空間で開放された状態が続いている描写があり、あれは酸欠どころか死んでいるんじゃないんでしょうか。

*8:F91もラストにシーブックとセシリーが宇宙空間で抱き合って終わりですが、F91のほうが遥かに爽やかです。

*9:ダブルゼータは最後まで食わず嫌いせずに観るべきです。たまにダブルゼータ黒歴史扱いする連中がいますが、そのような節穴の戯言は放っておきましょう。確かに前半の作風はおふざけが過ぎる部分もありますが、総括すれば間違いなく名作のひとつであり、クソ劇場版ゼータなど比べ物になりません。

*10:そもそもクソ劇場版は「ゼータガンダム」ではなく、ただのパチモン・贋作・偽物に過ぎないのですから、これからは「ゼットガンダム」と呼んで区別するべきでしょう。

*11:ガンダムUCにも言えるんですが、どうでもいいマイナーMSやハリボテのネオジオングといったクソどもを画面に出すヒマがあったら、もっと原作通り登場人物を掘り下げて欲しかったです。主人公バナージ・リンクスの人物像は監督の趣味趣向で捻じ曲げられた挙句、重要なシーンはカットされ、完全にオナニー販促アニメに成り下がってしまいました。

喫煙者は肺ガンで苦しみながら死んでくれ

最近窓を開けると、決まった時間にタバコの悪臭が漂ってくるようになりました。どうも新しくやってきた近隣の住民が吸っているようです。窓を閉め切っていれば悪臭が入ってくることはないのですが、自分はエアコンが嫌いで、夏場は換気扇と扇風機だけでしのいでいるので、タバコ臭はかなりウンザリします。夜中の12時を過ぎても漂ってきます。

タバコを吸う人間は全員肺ガンで例外なく苦しみながら後悔とともに死んでくれ。ちなみに痩せ型の人間は、肥満体型の人間よりも喫煙による健康リスクが高いそうです。同程度にリスクがあればいいのに。チッ。非喫煙者で痩せ型の人は特に受動喫煙副流煙に注意しましょう。最近よく見かける電子タバコや加熱式タバコは、目に見える煙は出なくても危険性に変わりはありません。とにかく喫煙者には近寄らないのが無難です。

自分の父親も恥ずかしいことに喫煙者ですが、ヤツは昔自家用車内で子供の前でも堂々と吸っていました。妻子の健康よりも、自分のヤニ切れ解消のほうが大事だったようです。運転席の窓を開けても腐った空気がかき混ぜられるだけで意味ねーよ。逆に臭いんだよ。エアフロー考えろよ。庭にタバコの吸い殻が捨てられているのを見て、タバコのフィルターは自然分解されにくいからポイ捨てするなとヤツに注意したこともありますが、結局聞く耳持たずでした。最低ですね。
弟も喫煙者で、残念ながらクソ親父の悪影響を受けてしまったようです。幼い頃は同じ環境で育った兄弟なのに、なぜタバコに対する考え方がこうも違ってしまったのか。まあそれ以外の点でもかなり性格や主義思想が違うんですけどね。

歩きタバコやポイ捨ては逮捕してくれ

歩きタバコ(路上喫煙)とポイ捨ても不快です。喫煙者の後ろを歩くと臭くてしょうがないのでかなり距離を空けます。吸い殻を目の前でポイ捨てされる場面に遭遇することはめったにないのですが、道端や側溝にまだ火のついたタバコが捨てられてあるのを見ると激しい殺意を覚えます。道路のど真ん中に転がっていることさえあります。あと歩きタバコは子供の目線の高さに火元がプラプラすることになるので、恐ろしい事故につながる可能性があります。できれば身をもってその危険性を教えてやるために、歩きタバコする喫煙者の手からタバコを取り上げてそいつの目玉に押し付けてやりたいですね。ジュッと。

路上喫煙やポイ捨てを取り締まる条例もありますが、地域によって違うとか、せいぜい罰金とか、日本は喫煙者に甘すぎです。迷惑どころか危険行為なので現行犯逮捕するべき。

飲食店はすべて完全禁煙にしてくれ

自分が今住んでいる地域では、日高屋とかいうやる気のない分煙(笑)をしているつもりのアホなチェーン店は除き、喫煙可能な飲食店はほとんど見かけず、かつて分煙だった飲食店も完全禁煙に移行したので喜ばしい限りです。サイゼリヤすかいらーくグループといった大手も今年から全店舗で完全禁煙になりました。ですが、今の職場のある新宿はひどいですね。チェーン店はたいてい禁煙か分煙ですが、ほとんどの喫茶店は普通にタバコ吸えます。時代遅れもいいところです。分煙すらされていないところは二度と行かないことにしています。臭くて何も楽しめない。分煙であっても喫煙席の近くは基本臭いです。だいたいタバコ吸って麻痺した連中の汚い舌や委縮した脳に何の味が分かるっていうんでしょうか。喫煙者は味覚・嗅覚・知能障害者です。

普段の飲食店は自分の意志で選べるので回避することもできるのですが、問題は飲み会ですね。隣が喫煙者だった日は地獄です。隣ではなくても部屋に喫煙者がいただけで、服が臭くなるのが不快です。健康被害を最小限に抑えるためにも、基本的に無意味な二次会などには参加せず、終わったらさっさと帰ります。

喫煙所は密閉・隔離してくれ

屋外の喫煙所には、簡易的な仕切りがあるだけで、エアフィルターを設けずに開放されているものがあります。ふざけんなと。福島原発放射性物質で汚染された地下水を薄めて海洋に放出しようとしたり、除染で発生した汚染土をこっそり日本中にばらまこうとしたりする日本政府というか安倍政権と同類の犯罪行為ですね。空も海も大地も、地球上のみんなのものです。共有財産を勝手に汚染するな。
そういえばこの前ヨーロッパに出張に行ったとき、地方の駅ではプラットフォームに喫煙所というか喫煙エリアがありました。フィルターどころか仕切りすらありません。かなり残念な光景でした。ていうかプラットフォームは完全禁煙にしろよ。

タバコは絶滅してくれ

喫煙者全員がマナーやモラルの低い人間だとは思いませんが、「百害あって一利なし」であることがすでに科学的に証明されているタバコを、自らの意志でやめることができないのは、ただの依存症です。タバコは趣味嗜好などではなく、アルコール中毒麻薬中毒と同じく病気です。病人は病院に行ってください。喫煙者がガンで苦しんで死のうが知ったこっちゃないんですが、奴らのせいでガン患者が増えて医療費として税金が無駄に使われていると思うと反吐が出ます。喫煙者のガンは治療する必要などありません。

喫煙者の人口割合が多かった時代に作られた昔の小説や映画、漫画やアニメなどでは、タバコを演出上の小道具のひとつとして重宝していたことがありました。とはいえ、文学や創作の世界にまで目くじらを立てるつもりはありません。タバコを吸っているけど個人的に気に入っている架空の登場人物、というのは自分にもあります*1。むしろ、かつてはそういう時代もあったんだね、ということを振り返ることができるという意味では貴重なので、昔の時代を描く場合はヘタに自重してタバコ演出を削除する必要はないと思います。

だが現実世界ではタバコは絶滅せよ。あとJTとかいう悪魔の手先は滅びろ。

*1:ブラック・ジャックとか、『恋は雨上がりのように』の店長とか、『野々村病院の人々』の海原琢麻呂とか。

東池袋自動車暴走死傷事故 厳罰化要望に署名しました

ameblo.jp

自分も微力ながら郵送にて署名に協力させていただきました。

署名用紙 (PDF) は以下からダウンロードできます。署名活動は8月末までを予定されているそうです。
このブログをご覧の方々、よろしければぜひ。

加害者の飯塚幸三は反省も謝罪もせず、それどころか権力で殺人を揉み消そうとしています。
そのような蛮行、決して許されるべきものではありません。

enumの読み方・発音はイニューム

ほとんどのプログラミング言語には列挙型 (enumeration type) あるいは列挙体 (enumeration) というデータ型があり、有限集合を順序付けて定義するのに使用します。C系の言語ではキーワードenumを使用します。

/* 軍艦の種類を表す列挙型 */
typedef enum MyWarshipType {
    MyWarshipType_BattleShip, /* 戦艦 */
    MyWarshipType_AircraftCarrier, /* 空母 */
    MyWarshipType_LightAircraftCarrier, /* 軽空母 */
    MyWarshipType_HeavyCruiser, /* 重巡洋艦 */
    MyWarshipType_LightCruiser, /* 軽巡洋艦 */
    MyWarshipType_Destroyer, /* 駆逐艦 */
    MyWarshipType_Submarine, /* 潜水艦 */
    /* C99 や C++11 では末尾要素の後のコンマが許可される */
} MyWarshipType;
/* 列挙型変数の例 */
MyWarshipType type = MyWarshipType_HeavyCruiser;

列挙型の各メンバーは列挙子 (enumerator) と呼ばれます。C/C++では各メンバーに整数値が割り当てられ、ユーザーコードで明示的に指定しなければ最初のメンバーの内部数値はゼロとなり、以降のメンバーは 1, 2, 3, ... のように自動的にインクリメントされた連番が割り振られます。

Javaには当初列挙型がありませんでしたが、有用性を認めたのか、Java 1.5で結局導入されました。ただしJavaの列挙型は参照型つまりclassの一種であり、そのままでは整数型と相互に変換することはできません。

C#には当然最初から列挙型があります。C#の列挙型はC/C++と相互運用しやすい値型となっていて、キャストだけで整数型と相互に変換することもできます。

C++ではC言語互換のenumをサポートするほか、C++11ではスコープを持つ列挙型 (scoped enum) が導入され、JavaC#に近い記述ができるようになりました*1

enumの発音

ところで、このenumをどう発音しますか?

もともとenumは、前述のように英語の enumeration から来ています。米国式発音では「inùːməréiʃən」、英国式発音では「injùːməréiʃən」です。もしカナで無理やり表記するとすれば、それぞれ「イヌーメレイション」「イニューメレイション」といったところでしょうか。アクセント(第1強勢)の位置は-tionの前、第4音節にあります。
また、enumerator の発音は「inúːmərèitər」もしくは「injúːmərèitər」であり、カナ表記するならば「イヌーメレイター」「イニューメレイター」といったところです。アクセントの位置は第2音節にあります。
動詞形 enumerate の発音は「inúːmərèit」もしくは「injúːmərèit」です。カナ表記するならば「イヌーメレイト」「イニューメレイト」といったところです。

したがって、enumは「inúːm」もしくは「injúːm」と発音するのが正しいと思います。カナ表記するならば「イヌーム」もしくは「イニューム」です。個人的には「イニューム」推し*2ですが、米語圏エンジニア相手の会話であれば「イヌーム」と発音するほうが伝わりやすいかもしれません。
ちなみに.NETにはIEnumeratorインターフェイスおよびIEnumerableインターフェイスが存在しますが、自分はそれぞれ「アイ・イニューメレイター」「アイ・イニューメラブル」と発音しています。
また、Windows APIにはトップレベルウィンドウを列挙する関数EnumWindows()が存在しますが、自分は「イニューム・ウィンドウズ」と発音しています。

たまに見聞きするのが、「イナム」とか「イーナム」とかいう発音ですが、これは正直ダサいと思います。略す前の原語を知らずに無理やり字面だけを読んでいるようで、個人的に言わせてもらうと教養を感じさせない、悪く言うと幼稚な印象さえ受けます。
ネイティブでもenumの発音に関しては諸説あり、「イナム」はともかく「イーナム」のほうはネイティブも使っている人がいるようで、誤りとまでは言えないようですが、個人的には嫌いな発音です。

というわけでenumイニューム。異論は認め……ない。どうしても「イーナム」と発音したい人は、せめてenumの語源と、enumeration, enumerator, enumerateの正しい発音くらいは把握したうえでどうぞお好きなように。

余談:charの発音

文字型charの発音に関しても意見が分かれるかもしれません。自分は語源の character そのまま、すなわち「kǽrəktər」=「キャラクター」と発音しています。Bjarne Stroustrupを始め、ネイティブでも字面だけを読んで「tʃɑ́ːr」=「チャー」とかいう人もいますが、これもまたダサいです。日本では「キャラ」と略す人もいるようですが、これは英語圏・米語圏では通用しないと思います。
ちなみにwchar_tは「ダブリューキャラクター・アンダーティー」もしくは「ワイドキャラクター・アンダーティー」と発音しています。

*1:このscoped enumは、もともとマイクロソフトが開発した.NET用マネージ言語C++/CLIにおいて、マネージ列挙型のために拡張された構文がベースになっています。

*2:自分の学部生の頃の恩師である助教授(当時)が「イニューム」と発音されていたので、その影響も少なからずあります。

ログ用に時刻を取得して文字列化する (C#, Java, POSIX/Win32)

アプリケーションのデバッグや、デプロイ後の問題解析に最も重要な役割を果たすのはログです。ログの機能や精度・粒度によって、問題解決のしやすさがほぼすべて決まります。また、ログは単なるトラブルシューティングだけでなく、場合によってはユーザー操作記録などの簡易的なエビデンス(証拠)としても使えます*1
通例、動作ログにタイミング情報を記録する際、レコード行に時刻情報を含めることで、状態遷移の正しさの確認や速度性能解析に役立てることができます。いわゆるprintfデバッグです。レコードの時刻情報はログをファイルに記録するときだけでなく、デバッグ出力するときにも役立ちます。

また、秒単位ではなくミリ秒単位の時刻情報が記録できると性能解析に便利です*2。なお、時刻は協定世界時 (UTC) で記録しておくと逆シリアライズが簡単になるというメリットがあるのですが、現地時刻(ローカル時刻)とタイムゾーン情報(GMTからのオフセット)を記録する方式のほうが、人間がログレコードを見て判断しやすくなるというメリットがあります。

ログファイルのフォーマットはパフォーマンスの観点から、ファイルの全書き換えではなくプレーンテキストの末尾追加で済むCSVフォーマットやTSVフォーマットなどが好ましいです。また、後で回収できるように、日付ごとにファイルを分けて作成したり、直近数か月分を保持して古いものは自動削除する循環ログにしたり、といった機能を実装します。自分はロギング機能を自前で実装したことが何度かありますが、通例 log4jlog4net を使っているプロジェクトも多いのではないかと思います。

今回はロギングの実装までは踏み込まず、C#, Java, C/C++におけるそれぞれの時刻情報取得と文字列化(フォーマット)についてのみ説明します。
ちなみにISO 8601は日付と時刻の間にTを入れるルールになっているのですが、今回は見やすさ優先のためASCII空白0x20にしました。

C#

C# (.NET) は後発なだけあって一番簡単です。洗練されていて美しいですね。余計な解説をするようなことはほとんどありません。

using System;

public class DateTimeFormatTest {
    public static void Main() {
        const string format = "yyyy-MM-dd HH:mm:ss.fffzzz";
        // GMT からのオフセット値が 0 の場合、タイムゾーンは "+00:00" になる。 
        Console.WriteLine("Local = " + DateTime.Now.ToString(format));
        Console.WriteLine("UTC   = " + DateTime.UtcNow.ToString(format));
    }
}

もしローカル時刻とUTC時刻との間の変換が必要な場合、TimeZoneInfo.ConvertTimeFromUtc()TimeZoneInfo.ConvertTimeToUtc()を使います。

なお、ラウンドトリップ書式指定子 "O", "o" を用いることで、簡単にISO 8601形式の書式化(シリアライズ)と解析(逆シリアライズ)ができるようです。

Java

Javaも時刻関連ライブラリは最初期から実装・標準化されています。しかしタイムゾーンの扱いが若干厄介な印象を受けます。ログのパーサーを書くときに考慮する必要があります。
注意点として、SimpleDateFormatはスレッドセーフではないので、スレッド間でインスタンスを使いまわすのは厳禁です。スレッドごとにインスタンスを作成する必要があります。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss.SSSXXX";
    public static void main(String[] args) {
        // GMT からのオフセット値が 0 の場合、タイムゾーンは "Z" になる。 
        System.out.println("Local = " + (new SimpleDateFormat(FORMAT, Locale.ROOT).format(new Date())));
        final SimpleDateFormat sdf = new SimpleDateFormat(FORMAT, Locale.ROOT);
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        System.out.println("UTC   = " + sdf.format(new Date()));
    }
}

C/C++

.NETもJavaもプラットフォーム非依存の高水準環境であり、標準ライブラリも充実しているので特に困るようなことはありませんが、問題はC/C++です。こいつらは標準化がとにかく遅い言語で、時刻関連の標準ライブラリは貧弱の一言です。まともな情報を取得するには、プラットフォームごとのライブラリやAPIを使わざるを得ません。

POSIX

UNIX時刻を表すtime_t型からtm構造体への変換がミソです。ちなみに逆変換をする関数mktime(), timegm()も存在します。

なお、標準Cライブラリのlocaltime(), gmtime()はスレッドセーフでないので使わないようにします。代わりにリエントラントなlocaltime_r(), gmtime_r()を使います。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#include <sys/time.h>

int main() {
    printf("sizeof(time_t) = %d\n", static_cast<int>(sizeof(time_t)));
    // 秒単位まででよければ、time() 関数を使う。
    //const time_t now = time(nullptr);
#if 0
    // C 標準では time_t の実際の型が整数であるかどうかは規定されていない。
    // POSIX では符号付き整数であることが保証される。
    if (time(&now) == -1) {
        puts("Failed to get time!!");
        return -1;
    }
#endif
    // gettimeofday() や clock_gettime() を使うと、マイクロ秒やナノ秒単位で時刻を求めることができる。
#if 0
    // timezone 構造体は廃止予定の非推奨機能。
    // gettimeofday() 関数は廃止予定の非推奨機能。
    struct timeval tvo = {};
    //struct timezone tzo = {};
    //gettimeofday(&tvo, &tzo);
    gettimeofday(&tvo, nullptr);
    const time_t now = tvo.tv_sec;
#endif

    struct timespec tso = {};
    if (clock_gettime(CLOCK_REALTIME, &tso) != 0) {
        puts("Failed to get clock time!!");
        return -1;
    }
    const time_t now = tso.tv_sec;

    // ctime_r() や asctime_r() は出力フォーマットがお粗末なので却下。
    struct tm tmo = {};
    if (localtime_r(&now, &tmo) != nullptr) {
        // tm_gmtoff, tm_zone は BSD 拡張。
        const int gmtOffsetHourPart = tmo.tm_gmtoff / 3600;
        const int gmtOffsetMinPart = abs(tmo.tm_gmtoff / 60) % 60;
        // ミリ秒部分を単純に "%03.0f" で四捨五入すると、例えば 999.9 の繰り上がりで破たんする。
        //const int msPart = static_cast<int>(tvo.tv_usec / 1000);
        const int msPart = static_cast<int>(tso.tv_nsec / 1000L / 1000L);
        printf("Local = %04d-%02d-%02d %02d:%02d:%02d.%03d%+03d:%02d\n",
            tmo.tm_year + 1900, tmo.tm_mon + 1, tmo.tm_mday, tmo.tm_hour, tmo.tm_min, tmo.tm_sec,
            msPart,
            gmtOffsetHourPart, gmtOffsetMinPart);
        // DST = Daylight Saving Time: 夏時間(サマータイム)。
        printf("WeekDay = %d, YearDay = %d, DST = %d, Zone = \"%s\"\n",
            tmo.tm_wday, tmo.tm_yday, tmo.tm_isdst, tmo.tm_zone);
        // +hhmm や -hhmm 形式でよければ strftime() を使うこともできる。
        char buf[128] = {};
        strftime(buf, sizeof(buf), "%F %T %z (%Z)", &tmo);
        printf("Local = %s\n", buf);
    }

    if (gmtime_r(&now, &tmo) != nullptr) {
        printf("UTC   = %04d-%02d-%02d %02d:%02d:%02d\n",
            tmo.tm_year + 1900, tmo.tm_mon + 1, tmo.tm_mday, tmo.tm_hour, tmo.tm_min, tmo.tm_sec);
    }
}

Win32

Visual C++のランタイムライブラリには、POSIXlocaltime_r(), gmtime_r()に相当するlocaltime_s(), gmtime_s()がMSVC 8.0以降のSecure CRTとして実装されていますが、引数や戻り値の仕様が異なるので注意が必要です。また、C11規格(C++11ではない)ではlocaltime_s(), gmtime_s()が追加されていますが、MSVCの同名関数とはインターフェイスが異なるので注意が必要です。ちなみに、MSVC 8.0以降のtime_tは64bit化されていて、32bitプラットフォームでも既定で64bit整数となり、2038年問題を回避できるようになっています*3

いずれにせよ、Windowsにはgettimeofday()clock_gettime()が実装されておらず、tm構造体のtm_gmtoff, tm_zoneメンバーも実装されていないので、ミリ秒単位で時刻を求めたり、タイムゾーン情報を取得したりする場合はWindows APIを直接叩くのがベストだと思います。

なお、TIME_ZONE_INFORMATION構造体のメンバーのうち、夏時間の扱いはよく分かりませんでした。日本はサマータイムとかいうクソシステムなんぞ採用していないのに、DaylightBias-60となります。

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <Windows.h>

int main() {
    setlocale(LC_ALL, "");

    SYSTEMTIME loc = {};
    ::GetLocalTime(&loc);
    SYSTEMTIME utc = {};
    ::GetSystemTime(&utc);

    TIME_ZONE_INFORMATION tzInfo = {};
    if (::GetTimeZoneInformation(&tzInfo) != TIME_ZONE_ID_INVALID) {
        const int gmtOffsetHourPart = -tzInfo.Bias / 60;
        const int gmtOffsetMinPart = abs(tzInfo.Bias) % 60;
        printf("Local = %04hd-%02hd-%02hd %02hd:%02hd:%02hd.%03hd%+03d:%02d\n",
            loc.wYear, loc.wMonth, loc.wDay, loc.wHour, loc.wMinute, loc.wSecond,
            loc.wMilliseconds,
            gmtOffsetHourPart, gmtOffsetMinPart);
        printf("WeekDay = %hd, Zone = \"%S\"\n", loc.wDayOfWeek, tzInfo.StandardName);
    }
    printf("UTC   = %04hd-%02hd-%02hd %02hd:%02hd:%02hd.%03hd\n",
        utc.wYear, utc.wMonth, utc.wDay, utc.wHour, utc.wMinute, utc.wSecond,
        utc.wMilliseconds);
}

説明の簡略化のためprintf系を使いましたが、C/C++で文字列バッファにフォーマット出力するときはsnprintf系やstd::stringstreamを使います。もしWindows限定でよければATL::CString::Format()を使うと簡単です。

Boost C++ライブラリのboost::posix_timeboost::date_timeを使えば、一応プラットフォーム非依存なコードを書けそうですが、JavaC#のように言語標準の方法で手軽かつ簡単に、というわけにはいきません。

ところで、C++11では時刻を扱うstd::chronoが追加されましたが、このライブラリは設計が大仰なわりに機能が貧弱で、しかも使い方が直感的でなくかなり面倒です。C++20で日付フォーマット関連の機能が追加されるらしいのですが、今更ですね。標準化が遅すぎる。C/C++JavaC#に比べて言語機能もライブラリも10年どころか20年以上遅れをとっています。生産性や移植性の低いC/C++は、21世紀における新時代のアプリケーション開発に使うべき言語ではありません。引退すべき言語とまでは言いませんが、今後はハードウェア層に近いバックエンドの記述などにとどめるべきです。

*1:もちろんコンプライアンス上の問題がない限り、という条件は付きます。ログにユーザーIDやパスワードを平文で記録するようなことは絶対にしてはいけません。最近の事例だとFacebookがやらかしていたことが報じられています。Facebookが数億件分のパスワードを暗号化しないままサーバーに保存していたことが明らかに - GIGAZINE

*2:システム時刻を処理時間計測に使うと、アプリケーション稼働中にシステム時刻が変更された場合に破たんするので、単調増加することが保証されるmonotonic (steady) なタイマーを使って2点間の差を計算するのがセオリーです。とはいえ、稼働中にシステム時刻の変更がないという前提であれば、システム時刻を処理時間計測に使うことはできます。

*3:逆に言うと、古いコンパイラコンパイル&ビルドされたアプリケーションは2038年問題を回避できません。

Android Javaのアサーション

昨年2018年は転職したこともあって、一番よく使った言語はJavaでした。とはいっても、ほとんどはAndroidJavaであり、サーバーサイドやデスクトップで使われている正式なJavaとはいささか性質が異なります。

JavaC/C++C#と違ってプリプロセッサをサポートしないのですが、そのせいで一番困るもののうちのひとつはアサーション(表明)です。アサーションは「契約による設計」(design by contract) を実現・実践するために有効なプログラミング手法であり、呼び出し元が保証すべき事前条件や、呼び出し先が保証すべき事後条件をコード中に記述し、条件が満たされなかった場合はプログラムを続行せずに中断します。一方、リリースビルド(本番環境)でアサーションが無効になっているときは、引数として渡した式(表明したい条件)が評価されずに実行コストゼロとなることが好ましく、そのためC/C++C#ではコンパイルオプションに応じて有効/無効が切り替わるプリプロセッサマクロやConditional属性付きメソッドによって、ゼロオーバーヘッドのアサーションを実現しています。逆に言うと、リリースビルドではアサーションの条件式は評価されないため、必ず実行されないと困るような式(システムに対して副作用のある式)は記述しないようにします。

#include <cassert>
// NDEBUG シンボルが定義されていない場合、assert マクロが有効になる。
// アサーションが有効な場合は assert マクロが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、abort 関数が実行される。
static void test(const int ary[], size_t length) {
    assert(ary != nullptr);
    for (size_t i = 0; i < length; ++i) {
        printf("%d\n", ary[i]);
    }
}

C/C++標準のassert()マクロは、表明が失敗 (assertion failure) するとabort()関数でプログラムを強制終了しますが、C#Debug.Assert()メソッドは表明が失敗した場合に無視してプログラムを続行することもできます。MSVCのCRTマクロ、_ASSERT()_ASSERTE()に近い挙動です。

// DEBUG シンボルが定義されている場合、Assert メソッドが有効になる。
// アサーションが有効な場合は Assert メソッドが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、エラーメッセージが表示される。
static void Test(int[] ary) {
    System.Diagnostics.Debug.Assert(ary != null);
    foreach (int x in ary) {
        System.Console.WriteLine(x);
    }
}    

Javaにはバージョン1.4で assert ステートメントが追加されたのですが、Javaアサーションコンパイルオプションではなく実行時オプションで有効/無効を切り替えます。JITコンパイラによる最適化がうまく機能すれば、無効時にはゼロコストとなることが期待できます。

// アサーションが有効な場合は assert ステートメントが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、java.lang.AssertionError がスローされる。
static void test(int[] ary) {
    assert ary != null;
    for (final int x : ary) {
        System.out.printf("%d\n", x);
    }
}

上記の例に限れば、仮にリリースビルドでnullが渡されたとしても、途中でjava.lang.NullPointerExceptionがスローされることでプログラムは中断されますが、nullを有効値として許可しないメソッドであるということをコード上で明らかにするためにもアサーションは有用です。Java 7以降であれば、java.util.Objects.requireNonNull()を併用するとなお良いです。ちなみにJSR 305の@Nonnullアノテーション (javax.annotation.Nonnull) はメソッドインターフェイスのドキュメント化に役立ちますが、すべての処理系で使えるわけではないのが残念です。

Android Javaの場合

しかし、Android 4.xまでの仮想マシン Dalvik では、アサーションの有効/無効を切り替えるシステム プロパティの debug.assert は信頼性がなく(無視されることがある)、さらにAndroid 5.0以降の仮想マシン ART (Android Runtime) では未実装とのこと。
Android StudioJava言語組み込みのassert文を使おうとすると、以下のような静的チェックツール (lint) による警告が出ます。

Assertions are not checked at runtime. There are ways to request that they be used by Dalvik (`adb shell setprop debug.assert 1`), but note that this is not implemented in ART (the newer runtime), and even in Dalvik the property is ignored in many places and can not be relied upon.
Instead, perform conditional checking inside `if (BuildConfig.DEBUG) { }` blocks. That constant is a static final boolean which is true in debug builds and false in release builds, and the Java compiler completely removes all code inside the if-body from the app.
For example, you can replace `assert speed > 0` with `if (BuildConfig.DEBUG && !(speed > 0)) { throw new AssertionError() }`.
(Note: This lint check does not flag assertions purely asserting nullness or non-nullness; these are typically more intended for tools usage than runtime checks.)
https://code.google.com/p/android/issues/detail?id=65183

つまり現在のAndroidでは、Java言語組み込みのassert文はまともに使用できません。
代替として、上記 lint では java.lang.AssertionError を明示的にスローするコードを書くことが提案されていますが、正直そんなまどろっこしいことやってられません。

Android開発環境が自動生成するクラスBuildConfigのboolean定数フィールドBuildConfig.DEBUGを使って分岐すれば、確かにコンパイラ最適化によりリリースビルドではゼロコストとなることが期待できるのですが、分岐&例外スローを逐一書くのがダルいので、自分はJUnitライクのアサーションユーティリティクラスと静的メソッドを作って対処しています。個人的には分岐やメソッド呼び出しのオーバーヘッドよりも、分岐&例外スローを逐一書くことでノイズとメンテナンスコストが増大することのほうが懸念だからです。なお、表明したい式を直接記述してbooleanで受け取るのではなく、ラムダ式で表現して遅延評価することにより、リリースビルドでの実行時コストを削減する方法もありますが、ラムダ式を使わない場合と比べてやはりコード上のノイズが増えます。

import java.util.function.*;

public class MyAssert {
    public static final boolean DEBUG = true; // BuildConfig.DEBUG で初期化する方式でも OK。

    public static void assertTrue(boolean condition) {
        if (DEBUG && !condition) {
            throw new AssertionError();
        }
    }

    public static void assertTrue(BooleanSupplier expression) {
        if (DEBUG && !expression.getAsBoolean()) {
            throw new AssertionError();
        }
    }
}

// 利用例。
MyAssert.assertTrue(ary != null);
MyAssert.assertTrue(() -> ary != null);

AndroidJavaは本物のJavaではなくJavaモドキでしかない、としばしば揶揄されますが、こういった非互換な仕様や動作には実際かなりウンザリさせられます。Oracleがブチ切れて訴訟を起こしたくなる気持ちも理解できます。自分にとってC/C++の一番嫌いなところは、処理系依存の仕様や未定義動作があまりに多すぎることですが、本来のJavaC#はそういった曖昧な仕様が極力排除されていることが最大の魅力のうちのひとつとなっています。仮にもJavaを名乗るのであれば、そういった根本的な設計思想を曲げるようなことをすべきではありません。これは技術者の倫理 (engineering ethics) に関わる問題です。

C++でstaticクラス

C#には「staticメンバーだけを定義して、実体化は許可しない」ようなクラスを簡潔に定義する手段があります。

public static class MyHelper
{
  public static int Add(int x, int y)
  { return x + y; }
}

staticクラスは状態を管理しない純粋ヘルパー(アルゴリズム)を定義するのに便利です。代表的な例でいうと、System.Mathクラスが挙げられます。
なお、staticクラスからはサブクラスを派生させることもできません。

ちなみにJavaでもクラスをstaticキーワードで修飾することができますが、意味合いがまったく違います。C#のほうがキーワードに即していて直感的だとは思いますが。

C++/CLIおよびC++/CXでは以下のように書くことでC#のstaticクラス相当を記述できます。

public ref class MyHelper abstract sealed
{
public:
  static int Add(int x, int y)
  { return x + y; }
};

しかし、従来の標準C++では、staticクラスを記述する文法は直接サポートされていません*1
せいぜいできることと言えば、以下のようにデフォルトコンストラクタとデストラクタを隠ぺいして実体化を禁止するくらいしかなく、C#と比べて分かりやすさはともかく美しさに欠けます。
また、派生を禁止するにはC++11以降のfinalキーワードを使う以外ありません。

class MyHelper final
{
private:
  MyHelper() {}
  ~MyHelper() {}
public:
  static int Add(int x, int y)
  { return x + y; }
};

MSVCにはネイティブC++でもabstractキーワードを使えるように言語拡張が施されているので、abstract sealedまたはabstract finalを指定することでstaticクラスを記述することができるのですが、Clangなどでは使えません。移植性の高いコードを書きたい場合は注意が必要です。

*1:C++では必ずしもクラスに関数を定義する必要はなく、クラスに属さない名前空間レベルの関数を定義することもできるので、これまでstaticクラスのサポートが重要視されてこなかったのかもしれません。