くまくまの業務日誌

Markdown記法で徒然に書いてみましょう。

ハンガリアン記法について

ハンガリアン記法はオワコンなのか?

そんなことはない、現時点でもgithubで検索すると、現在進行中のプロジェクトで、Linux/Windows両対応のソースコードにもまだまだ使用されている。ただ、提唱者の意図していない形になってしまったのは残念ではあるが。

ハンガリアン記法のルールは?

基本は、変数名の先頭(プレフィックス)に付けるキーワードのルールとなる。型が複数(ポインタなど)になってくると、プレフィックスも複数になってくる。

■ルール1:スコープに応じたプレフィックスをつける。

WIN32APIの仮引数で登場した記法なので、グローバル変数に関しては見たことがない。

”m_" は、今でもよく見るルールである。MFCでは、MS製ソースコードもサンプルでよく見たが、ほとんどこれが記載されていた。

  • グローバル変数…"g_"をつける
  • メンバー変数..."m_"をつける
  • ローカル変数...以下で説明
  • 関数の引数...ローカル変数のルールに準ずる。

ただ、もっとシンプルにしたいと願うのも確かである。以下のルールでも十分役目を果たせると思うが、如何だろうか。

  • グローバル変数は "__" アンダーバーを2つ付ける。
  • メンバー変数は "_" アンダーバーを1つ付ける。

■ルール2:ポインタの場合は "lp" ないし、"p" をつける。

今の時代に "near" だの "far" だの、意味があるのだろうかと思うわけだが、 "l" はロングのLで、意味するところは、near/far の far の方を指していたのは、はるか昔のWIN16APIの話となる。現在のVisual Studioでは、near/far は無意味である。だが、Visual Studio で "LPCVOID" と記載して、右クリックして、「定義へ移動」を選択してみて欲しい。"typedef CONST void far LPCVOID;"の行にたどり着いたと思うが、そのもうちょっと上の行にはまだ、「near」の文字が散見される。 "typedef BOOL near PBOOL;" の行を見て、「今どき "far" もないよな。」と "LP" でなく、"P" を使用した場合、もれなく "near" が付いてくることになる。 これを見て、ポインタ宣言の時は "P" ではなく、"LP" で宣言するようにしている。重ねて言うが、"near"は意味がないキーワードである。

昔(WIN16API)の名残で、"lp" と "p" の両方をWindowsのヘッダーファイルは宣言しているが、実は1つないものがある。 LPCVOIDは宣言されているが、PCVOIDはないのである。よく使いそうなのにない。これも、自作関数で "P" に統一できない理由である。

■ルール3:const などの定数の場合は、"c"をつける。

const もWindowsでは、"CONST" と宣言されているので、これを使う。

■ルール4:wide char/multi byteの両方を意識した変数の場合は "t"をつける。

UNICODEの宣言あり/なしで変数(実は関数も変わる)の型が変わる場合、プレフィックスとして "t" を付与する。 予想だが、"transparent:透過的"の t ではなかろうか。 UNICODE関数の場合は、文字数を返したり、wchar_t*型を返してくることになるので、サイズや内容操作時は注意が必要である。

■ルール5:ここからは、変数毎の命名規約になる。

ハンガリアン記法とは全く関係がないが、Windowsはプリミティブな変数に対しても、大文字で再定義しているものが多い。将来を見越しての対処だったのだと思うが、今までスムーズに16bit~32bit~64bitに遷移してこれているので、杞憂だったのではないか。しかし、ほとんどのWIN32APIはこの大文字変数型を使用しているので、これに追従していくしかない。

■■8bit変数の場合

変数名
cSizeCHAR
byBufferUCHAR/BYTE
// winnt.h
typedef char CHAR;
// minwindef.h
typedef unsigned char UCHAR;
typedef unsigned char       BYTE;

■■16bit変数の場合

変数名
sSizeSHORT
wParamUSHORT/WORD
// winnt.h
typedef short SHORT;
// minwindef.h
typedef unsigned short USHORT;
typedef unsigned short      WORD;

■■32bit変数の場合

変数名
iDataSizeINT
uiPathLengthUINT
lTableSizeLONG
ulAreaSizeULONG
dwBufferLenDWORD

Windowsは "long"の宣言でも32ビットである。逆にLinuxは "long" の宣言で64ビットとなる。
https://marycore.jp/prog/c-lang/data-type-ranges-and-bit-byte-sizes/

// minwindef.h
typedef int                 INT;
typedef unsigned int        UINT;
typedef unsigned long ULONG;
typedef unsigned long       DWORD;
// winnt.h
typedef long LONG;

■■64bit変数の場合

  • signed long long の場合、"ll" をプレフィックスに付ける。宣言は LONGLONG を使う。
  • unsigned long long の場合、"ull" をプレフィックスに付ける。宣言はULONGLONG を使う。

※32Bitの時代に生まれたハンガリアン記述のため、64Bitに関しては諸説あり、な状況である。

// winnt.h
typedef __int64 LONGLONG;
typedef unsigned __int64 ULONGLONG;

WIN32で64ビットを扱うために、以下の宣言を使用する場合があるが、これはユニオンである。

LARGE_INTEGER は、ユニオン(_LARGE_INTEGER)をtypedef宣言したもので、以下のようになっている。

typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER;

同様にULARGE_INTEGER も、以下のように宣言されている。

typedef union _ULARGE_INTEGER {
    struct {
        DWORD LowPart;
        DWORD HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart;
        DWORD HighPart;
    } u;
    ULONGLONG QuadPart;
} ULARGE_INTEGER;

で、LowとHighをどう使い分けるのか?
これは、入れる場合と、出す場合を統一すれば、問題ない。ここであえて、エンディアンを考える必要はない。
もし、Intel系のビッグエンディアンで作成して、非Intel系のリトルエンディアンで取り出す場合を想像しているなら、それは16ビットの変数から検討しなければならない。

■■実数の場合

※なぜ、doubleに対して"DOUBLE"を用意しなかったのか?

変数名
fLeftPosFLOAT
dMaxWidthdouble

■■bool変数の場合

変数名
bShowWindowBOOL

なお、BOOLはint型である。

// minwindef.h
typedef int                 BOOL;

■■void型の場合

変数名
lpvParamLPVOID

宣言で使用するVOIDは主にアドレスの取得として用いられる。32Bit/64Bitの影響をモロに受ける変数であるが、そもそもサイズ感のない変数なので32→64に換装する場合は要注意変数である。

■■文字列の場合

文字列の場合、基本的には「ヌルターミネーション」であることが、C/C++の標準なので、一位にプレフィックス"sz"が決まる。
ちなみに、string zero termination の省略形である。

通常の文字列とは違う形式としてBSTR型が存在する。BSTR型の文字列の持ち方は特殊で、先頭に文字列長、その後に文字列、という持ち方をしている。この変数の場合、NULLで終端を閉じていないため、'z'は付かないことになる。

変数名
szPathchar [ ]
tszPathwchar_t [ ]

■WIN32APIによく登場する変数宣言

変数名
cSizechar/CHAR
sParamshort/SHORT
iCounterint/INT
lpvParamvoid*
lpcszFilePathchar*/CHAR*
lpctszFilePathconst char*/CONST CHAR*
szBufferchar [ ]
tszBufferwchar_t [ ]

cSize sParam iCounter lpvParam lpcszFilePath lpctszFilePath