2019年3月1日金曜日

整理しよう整数型

いつのまにか 64ビットアプリが当たり前になり、Lazarus/Delphi でも 64ビットアプリを当たり前のように作成できるようになりました。
将来的には、Mac のように64ビットアプリいっぽんに統一されていくのでしょうが、Windows の 32ビット版が淘汰されるまでは相当時間がかかると思われますので(未だに Windows XP や IE が現役ですからねぇ)、今のところ我々エンジニアは、ソースを32ビットでも64ビットでも正しくコンパイルできるようにしておく必要があります。
その際に最も問題になるのは「整数型としてどの型を使うか」という点ではないでしょうか。JavaScript のようなスクリプト型の言語ですと、数値型には整数型と浮動小数点型の2つしかなかったりしますので悩まなくてもよいのですが(悩みたくても悩めないというのが正解でしょうか)、Lazarus/Delphi/C のようなコンパイル型の言語には、整数型がたくさんあるため、きちんと使い分ける必要があります。
そこで、整数型について整理して、おすすめのルールを作成してみました。

おすすめルール1

まず、32ビット整数の範囲で収まる変数については、次のように NativeInt、NativeUInt を使うのが最強でしょう。こうすることでコンパイラ生成コードが最適化されやすくなるはずです。

var i: NativeInt;
var u: NativeUInt;

もっとも、型名が長いので、次のように別名を定義するとよいでしょう。

type
  int = NativeInt; uint = NativeUInt;
  pint = ^int; puint = ^uint; // ついでにポインタ参照型も宣言しとく

var i: int;
var u: uint;

おすすめルール2

次に、32ビットを超える値を扱う変数については、NativeInt や LongInt のような「プラットフォーム依存の整数型」を使うのは避け、次のように Int64 型や Uint64 型を用いて、32ビットを超える値を扱う変数であることを明示的にするのがよいでしょう。

var i: int64;
var u: uint64;

おすすめルール3

さらに、ファイルに保存するための領域など、サイズを固定化すべきところでは、NativeInt や LongInt のような「プラットフォーム依存の整数型」を使うのは避けるべきでしょう。次の例は失敗例です。

var buf: longint;
stream.write(buf, sizeof(buf));

これではプラットフォームが違うと読み込めえないファイルが作成されてしまいます。
おそらくこの結果は多くの方が望むものではないはずですので次のようにすべきでしょう。

var buf: int32;
stream.write(buf, sizeof(buf));

なお、integer 型も「プラットフォーム依存の整数型」ではありませんので、integer 型を使ってもよいのですが、int32 型の方がパッと見で変数領域サイズが分かりやすいのでよいと思います。

おすすめルール番外編

「ルールが3つもあるの面倒!」という方は、「NativeInt や LongInt のようなプラットフォーム依存の整数型は絶対に使わない!」というルールだけでも十分です。ようは「プラットフォーム依存の整数型」とそれ以外の整数型をしっかり区別して使い分けできていればいいだけなのですから。

最後に

いかがでしたでしょうか。
「プラットフォーム依存の整数型」とそれ以外の整数型の区別について詳しくお知りになりたい方には http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/%E5%8D%98%E7%B4%94%E5%9E%8B%EF%BC%88Delphi%EF%BC%89 が便利かと思います。


2016年6月24日金曜日

Lazarus 1.6(FPC3.0)の文字列自動変換は使用しないのが吉



僕は以前、FPC 3.0 では、コードページが異なる文字列の代入時に FPC が文字コードの変換を自動的に行うようになり、便利だよというようなことを記事にしました

しかし、結論からいえば、この「自動変換機能」は使用しないほうがよいです。いろいろテストしてみたのですが、理解しにくい現象が発生することがあり、現時点ではとてもおすすめできる機能ではありません。
とはいってもこの機能が使えないからといって何も悲観的になる必要はありません。よく考えて見れば、自動変換ってそれほど便利なものなのでしょうか。以前の記事で僕は「Decode や Encode という、どっちがどっちかよくわからない命名に悩まなくてよくなるだけでなく、ソースファイルのコードページが UTF8 でない場合にも対応できるようになります。」と書きました。これは結局「文字コードが何かをプログラマーが気にする必要はない」という夢の様な話をしているのですが、経験のある方ならご存知のとおり、使用している文字コードが何かすら把握できていないようなソースコードほど保守が厄介なものはありません。特に、昔から色々な文字コードを使用してきた僕達日本のエンジニアはそのことをよく分かっています。
今までどおり、文字コードの変換には UTF8Decode() や UTF8Encode() などを使いましょう。そうです何も変える必要はないのです。むしろ、Lazarus のバージョンによってソースコードを変えるという厄介な事態が回避できて嬉しいくらいです。

話が長くなるのであまり多くは書きませんが、「自動変換機能」が使えない諸悪の根源は、簡単にいってしまえばコンパイル時の文字コードと実行時の文字コードがこちらの意図とは異なるものになってしまう場合があるからです。

さて、繰り返しますが、我々は、Lazarus 1.4 で書いたソースコードをわざわざ Lazarus 1.6 の新しいコードページ文字列のために書き換える必要はありません。具体的なまとめとしては次のことを守れば、「自動変換機能」によって生じうる異常現象を回避できます。
  1. UTF8文字列とUTF16文字列の相互変換には、今までどおり UTF8Decode() や UTF8Encode() などを明示的に使う。
    例えば、間違っても、次のようなコードは書かないでください。

    var s: UnicodeString;
    s:='あいうえお';

    UnicodeString 型(およ WideString 型)の文字コードは UTF16 で、'あいうえお' の文字コードは UTF8 ですから、今までどおり、

    s:=
    UTF8Decode('あいうえお');

    と、してください

  2.  UTF8String 型は使用しない。
    UTF8String 型を使うと異常現象に悩まされることがあります。現時点では UTF8String 型を使用するメリットは何もありません。素直に string 型を使用してください。
    Lazarus 1.4 では string 型と UTF8String 型は完全に同じものでしたが、Lazarus 1.6 では両者は異なるものになりました。あまりいないとは思いますが、Lazarus 1.4 で UTF8String 型を使っていた方は string 型に変更したほうが良いです。

ユニコード文字列の取り扱い (訂正版)

以前の記事の訂正版です。Lazarus 1.6 の完成により事情もだいぶ変化したので、内容を少し改め分かりやすくしたほうがよいと思い、訂正しました。
Lazarusの標準文字コードはUTF8です。したがって、森鷗外の「鷗」のように、ユニコードにはあるがシフトJISにはない文字もあっさり使用できます。例えば、Label.Caption := '森鷗外'; のように。
そして、Lazarus には Delphi と同じく、ユニコード文字列を取り扱うためのルーチンが豊富に用意されています。したがって、自分でコーディングしてユニコード文字列を処理する場面では困ることはないと思います。

Lazarus 1.4 には次のような問題がありました。 
それは、LCLやRTLなどにある既存のルーチンでのユニコード文字列の取り扱いです。代表的なものはファイルのパスで、例えば 多くのオブジェクトに搭載されている LoadFromFile メソッドには、パスをシフトJIS(ANSI)で渡さなければいけません。 したがって、 LoadFromFile(UTF8ToSys(filename)) のようにして、UTF8をシフトJISに変換して渡す必要があります。当然、ファイル名に、"森鷗外.txt"のようなユニコードにはあるがシフトJIS にはない文字が含まれていた場合はエラーになります。これは、内部で Windows API をコールする際に、シフトJISしか使えないAPI、いわゆるA系のAPIを使っていることが主たる原因です。
この問題を解決するため、lazutf8classes や FileUtil ユニットなどに、ユニコード文字列に対応したルーチンが準備されています。その多くは、ParamStrUTF8やFileExistsUTF8、 TFileStreamUTF8、TStringListUTF8 など、標準名+"UTF8"という名前がつけられています。これらが Windows API を使用する場合は、ユニコード文字列に対応したいわゆるW系のAPIを呼び出してくれます。
しかし、これで問題が全て解決するわけではありません。先程の例である LoadFromFile メソッドのほとんどでは内部でTFileStreamUT8ではなくTFileStreamを使用しているため、結局ユニコードのパスは使用できません。 また、UTF8対応の関数を使う必要があるかという判断には、その関数がどういう処理をするのか、シフトJISとUTF8の違いはなんなのか、という知識 が求められることになります。

繰り返しますが、以上は Lazarus 1.4 でのお話です。 Lazarus 1.4 は、FPC(=Free Pascal Compiler) 2.6 をコンパイラとして使用するのですが、FPC 2.6 の基本ルーチンがANSIしか処理できないため上記のようになってしまっています。FPCに依存せずに Lazarus側が全て自前でユニコード処理をしてしまえばよいわけですが現実的ではないので、lazutf8classes や FileUtil ユニットなど最低限のものを苦肉の策としてLazarus側で準備しているわけです。
これに対し、FPC 3.0 では、ユニコード標準対応を謳っています。誤解をおそれずに簡単にいってしまえば、多くの基本ルーチンで、引数がANSI文字列型の場合はA系APIを、ユ ニコード文字列型の場合はW系APIを適切にコールしてくれるようになります。これによって、上記の問題は完全に解決されます。そして、Lazarus 1.6 では、FPC 3.0が Lazarus の標準コンパイラに採用されました。

Lazarus 1.4 で作成した自前のソースを Lazarus 1.6 でビルドすると、UTF8ToSys関数、 lazutf8classes や FileUtil ユニットなどのUTF8対応ルーチンを使用している箇所でWarningが出るので修正すべき場所がすぐ分かります。UTF8ToSys関数はもはや不要 になりますし、TFileStreamUTF8などはUTF8を取ってTFileStreamを使えば良いことになります。


2016年4月14日木曜日

三項演算子です


C++では、三項演算子(ternary operator)が使えます。次のようなものです。

x = a ? b : c;

解り難いとかの批判もあるみたいですが、それは条件等が複雑な場合だけで、そうでない場合は逆に if 文よりも解りやすいと思うのですがいかがでしょう。
果たして Lua でこれを実現できるのでしょうか?
Lua のリファレンスマニュアルには次のように書かれています。

論理積演算子 and は最初の引数が falsenil であればその値を返し、そうでなければ2番目の引数を返します。 論理和演算子 or は最初の引数が nil でも false でもなければその値を返し、そうでなければ2番目の引数を返します。 andor は両方とも短絡評価を行います。 つまり2番目の引数は必要な場合にだけ評価されます。
 ~ Lua 5.3 リファレンスマニュアル - 論理演算子
The conjunction operator and returns its first argument if this value is false or nil; otherwise, and returns its second argument. The disjunction operator or returns its first argument if this value is different from nil and false; otherwise, or returns its second argument. Both and and or use short-circuit evaluation; that is, the second operand is evaluated only if necessary.
 ~ Lua 5.3 Reference Manual – Logical Operators
よって、

x = a and b or c

これで三項演算子が実現できます。ただ、慣れないうちは三項演算であることが分かり難いかもですね。
なお、C++ では数値の 0 は false と判定されますが、Lua では true と判定される点には注意してください。


2015年12月18日金曜日

「高度なレコード型」は要らない子

Lazarus や Delphi には、「Object 型(Object Types)」というものがあります。(参照: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Classes_and_Objects#Object_Types )

とても便利な機能なのですが、あまりにも普通で抽象的すぎる名前を付けてしまったことを後悔しているのか、Delphi では非推奨の機能とされています。

しかし、何度でもいいますが Object 型は便利です。そのことに中の人も気付いたのでしょうか、Delphi では同じ機能が「高度なレコード型(Advanced Record)」として追加されました。(参照:http://docwiki.embarcadero.com/RADStudio/Seattle/en/Structured_Types#Records_.28advanced.29

FPC でも、ソースの先頭付近に、


{$modeswitch ADVANCEDRECORDS}
 

を追加すれば「高度なレコード型」を使用できます。
FPC では、「Object 型」は非推奨の機能ではありませんので、「高度なレコード型」の方がオプション扱いなのでしょう。
個人的には、「高度なレコード型」よりも「Object 型」を使ったほうがよい気がします。なぜなら、「高度なレコード型」は「承継(inherited)」がサポートされていないなど、どちらかというと「Object 型」の劣化版といえそうだからです。

「高度なレコード型」や「Object 型」の便利なところは、「Class型(Class Types)」とは異なり、宣言するだけで実体が作成され、スコープから抜けると実体が破棄されるところです。


type
  TMyStruct = object
  public
    item: integer;
  end;

var
   my: TMyStruct;
begin
  my.item := 100;
  ShowMessage(IntToStr(my.item));
end;

上で紹介済みの http://docwiki.embarcadero.com/RADStudio/Seattle/en/Classes_and_Objects#Object_Types では、「Object 型」は New と Dispose で生成と破棄ができると書いてあります。生成と破棄のタイミングを管理したいこともたしかに多いですが、それなら「Class型」を使えば十分ですから、「Object 型」で New と Disposeを使用するメリットはないでしょう。

ところで、「高度なレコード型」や「Object 型」で少々混乱しやすいのは、実体の作成とコンストラクタの関係です。

type
  TMyClass = class
  public
    item: integer;
    constructor Create(aItem: integer);
  end;

  TMyStruct = object
  public
    item: integer;
    constructor Create(aItem: integer);
  end; 

  :

var
  myclass: TMyClass;
  myobject: TMyStruct;
begin
  myclass:=TMyClass.Create(100);
  mystruct:=TMyStruct.Create(100);
  :
  myclass.Free;
end;

「Class型」の場合は、コンストラクタにより実体が作成されるのでイメージをつかみやすいですが、「Object 型」は var で宣言するだけで実体が作成されるのでコンストラクタという概念がいまいちピンときません。
個人的には、「Object 型」におけるコンストラクタは単なる「クラスメソッド(Class Method)」に過ぎないので、コンストラクタは使わず「クラスメソッド」を使えば十分だと考えています。

type
  TMyStruct = object  public
    item: integer;
    class procedure Create(aItem: integer);
  end; 
  :

var
  myobject: TMyStruct;
begin
  mystruct:=TMyStruct.Create(100);   :
end;

おそらく、「Object 型」におけるコンストラクタやデストラクタは、既述の New や Dispose で真価を発揮するものだと思います。そして既述のように、New や Dispose を使うくらいなら「Class型」を使った方がよいですから、「Object 型」ではコンストラクタやデストラクタの出番はないということになります。

・・・そうとすると、 「高度なレコード型」でもコンストラクタが使えるみたいなのですが、おそらくその出番はないでしょう。

2015年12月11日金曜日

TRect のお話

Lazarus や Delphi では矩形領域を現す TRect という便利な型とそれを操作するための便利な関数群が用意されています。
TRect は、上下左右の座標で定義されますが、標準では、下と右は+1してあげる必要があります。例えば、画面上の左上隅の1ドットを TRect で現すと Left = 0, Top = 0, Right = 1, Bottom = 1 となります。 もちろん、自己のプログラムの中で1ドットの矩形は Left = Right, Top = Bottom とするんだ!とすることは自由なわけですが、少なくとも Lazarus や Delphi の標準ルーチンはそのように処理しません。例えば 、矩形が空かどうかを調べる IsRectEmpty 関数は次のようになっています。

function IsRectEmpty(const Rect : TRect) : Boolean;
begin
  IsRectEmpty:=(Rect.Right<=Rect.Left) or (Rect.Bottom<=Rect.Top);
end;

左右あるいは上下の座標が同じなら空と判定してますね。

2015年11月5日木曜日

Lua でテーブルの内容を知りたいときは次のようなコードを使用します。

for k, v in pairs(table) do
  print(k .. " = " .. v)
end


pairs関数により、k にはキーが、vにはそのキーの値が返されます。

すべてのグローバル変数は _G というテーブルに格納されています。よって、

for k, v in pairs(_G) do
  print(k .. " = " .. v)
end


とすると全てのグローバル変数の情報を知ることができます。Lua では関数も全て変数として扱われますので、FlashAir などのような Lua を使える機器の隠しコマンドを発見するのに便利です。