C99に対応した標準Cライブラリの実装レポートを行っていきます。

プロフィール 

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

友達申請フォーム 

この人と友達になる

ホーム 全記事一覧 次ページ >>

 

2008/07/06 21:07|

 

前回に引き続き<stdint.h>についてです。今回は、<stdint.h>内で定義されるマクロに関してお話します。

まずは、intN_t型、uintN_t型、intmax_t型、およびuintmax_t型の定数を作るためのマクロです。実際には、それらの型に完全に合致するわけではなく、それらの型を汎整数拡張した型にするものです。これらのマクロは、C++では、__STDC_CONSTANT_MACROSマクロが定義されている場合のみ定義されるので、

#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS)
/* 内容 */
#endif

のようにしなければなりません。そして、実際のマクロ定義は、

#define INT8_C(n)    (n)
#define UINT8_C(n)   (n)
#define INT16_C(n)   (n)
#if __INT_MAX__ == 32767
#define UINT16_C(n)  (n##U)
#else
#define UINT16_C(n)  (n)
#endif
#define INT32_C(n)   (n##L)
#define UINT32_C(n)  (n##UL)
#define INT64_C(n)   (n##LL)
#define UINT64_C(n)  (n##ULL)
#define INTMAX_C(n)  (n##LL)
#define UINTMAX_C(n) (n##ULL)

のようになります。ここで、UINT16_Cに関しては、int型が16ビットの場合には、unsigned int型にしなければなりませんので、添え字の U を付加しています。

次は、最大最小値に関するマクロです。これらのマクロは、C++では、__STDC_LIMIT_MACROSマクロが定義されている場合のみ定義されるので、

#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS)
/* 内容 */
#endif

のようにしなければなりません。それでは、実際のマクロ定義を見ていきます。<stdint.h>で定義した型に関しては、定義に用いた基本型の最大最小値と同じになるので、<limits.h>のときと同様に、

#define INT8_MAX   127
#define INT8_MIN   (-128)
#define UINT8_MAX  255
#define INT16_MAX  32767
#define INT16_MIN  (-32767-1)
#define UINT16_MAX 0xffff
#define INT32_MAX  __LONG_MAX__
#define INT32_MIN  (-__LONG_MAX__-1)
#define UINT32_MAX (__LONG_MAX__*2UL+1)
#define INT64_MAX  __LONG_LONG_MAX__
#define INT64_MIN  (__LONG_LONG_MAX__-1)
#define UINT64_MAX (__LONG_LONG_MAX__*2ULL+1)

#define INTMAX_MAX  __LONG_LONG_MAX__
#define INTMAX_MIN  (__LONG_LONG_MAX__-1)
#define UINTMAX_MAX (__LONG_LONG_MAX__*2ULL+1)

#if defined(__H8300__) || defined(__NORMAL_MODE__)
#define INTPTR_MAX  __INT_MAX__
#define INTPTR_MIN  (-__INT_MAX__-1)
#define UINTPTR_MAX (__INT_MAX*2U+1)
#else
#define INTPTR_MAX  __LONG_MAX__
#define INTPTR_MIN  (-__LONG_MAX__-1)
#define UINTPTR_MAX (__LONG_MAX*2U+1)
#endif

と定義することができます。

<stdint.h>では、他のヘッダで定義される整数型の最大最小値も定義する必要があります。まずは、size_t型の最大値です。<stddef.h>では、size_t型を__SIZE_TYPE__マクロを使って定義しましたが、#if指令で使用できる定数にするためにはキャストが使えないため、ここではsize_t型の実際の型を知る必要があります。そこで、

echo __SIZE_TYPE__ | h8300-hms-gcc -E - [オプション]

といったコマンドを使って、__SIZE_TYPE__の定義内容を調べました。すると、H8/300、およびH8/300HとH8Sのノーマルモードでは、unsigned int型、それ以外(H8/300HとH8Sのアドバンストモード)ではunsigned long型でした。したがって、

#if defined(__H8300__) || defined(__NORMAL_MODE__)
#define SIZE_MAX (__INT_MAX__*2U+1)
#else
#define SIZE_MAX (__LONG_MAX__*2UL+1)
#endif

と定義することができます。

次に、ptrdiff_t型の最大最小値ですが、同様の方法で__PTRDIFF_TYPE__の定義内容を調べると、やはり、H8/300、およびH8/300HとH8Sのノーマルモードでは、int型、それ以外ではlong型でした。したがって、

#if defined(__H8300__) || defined(__NORMAL_MODE__)
#define PTRDIFF_MAX __INT_MAX__
#define PTRDIFF_MIN (-__INT_MAX__-1)
#else
#define PTRDIFF_MAX __LONG_MAX__
#define PTRDIFF_MIN (-__LONG_MAX__-1)
#endif

のように定義することができます。

次は、wchar_t型の最大最小値ですが、やはり同様に__WCHAR_MAX__の定義内容を調べると、オプションに関わらずunsigned short型でしたので、

#define WCHAR_MAX 0xffff
#if __INT_MAX__ == 32767
#define WCHAR_MIN 0U
#else
#define WCHAR_MIN 0
#endif

のように定義することができます。WCHAR_MINは、int型が16ビットの場合はunsigned int型に、32ビットの場合はint型に評価されなければならないため、上記のようにしています。

最後が、sig_atomic_t型の最大最小値です。sig_atomic_t型は、その型のオブジェクトに対する読み書きがアトミックオペレーションで行われなければなりません。これはプロセッサの命令セットに依存します。

H8/300の場合、汎用レジスタのサイズは16ビットなので、sig_atomic_t型も16ビット以下でなければなりません。H8/300HおよびH8Sは汎用レジスタのサイズが32ビットありますので、32ビット以下の型にすることができます。したがって、H8/300では、sig_atomic_t型をshort型に、それ以外はint型に定義することにしました。したがって、

#ifdef __H8300__
#define SIG_ATOMIC_MAX 32767
#define SIG_ATOMIC_MIN (-32767-1)
#else
#define SIG_ATOMIC_MAX __INT_MAX__
#define SIG_ATOMIC_MIN (-__INT_MAX__-1)
#endif

と定義することができます。

これで<stdint.h>の実装も終わりです。そしてこれにより、C99に対応した、フリースタンディング環境で必要とされる全ライブラリの実装が終わったことになります。次回からは、ホスト環境互換のライブラリに手を付けていきます。
2006/01/27 01:46|処理系の特性TB:0CM:0

 

<stdint.h>は、特定の幅を持つ整数型と、それらに対応するマクロを定義しているヘッダです。このヘッダもC99から新しく導入されました。int16とかint32とかいったマクロは、既存の多くのソースコードで使用されていますが、それらに標準的な仕様を与えることが目的のようです。

まずは、ちょうどその幅を持つ整数型の定義です。"h8300-hms"ターゲットでは、char型は8ビット、short型は16ビットですので、

typedef signed char    int8_t;
typedef unsigned char  uint8_t;
typedef short          int16_t;
typedef unsigned short uint16_t;

というところまでは確実です。
32ビットの整数型は、int型が32ビットかどうかにもよりますが、規格を読む限りでは、int型とlong型の両方が32ビットの場合には、どちらを使ってもよさそうなので、long型にしておきます。

typedef long          int32_t;
typedef unsigned long uint32_t;

64ビットの整数型ですが、2つ問題があります。ひとつは、gcc-3.3.x以前のバージョンでは、"h8300-hms"ターゲットではlong long型が32ビットしかなかったことです。これについては、旧バージョンに対応するとなると影響範囲が大きいので、今回は見送ることにします。

もうひとつは、-std=c89オプションと-pedanticオプションの両方を指定した場合、long long型を使うと警告が出る問題です。この問題は__extension__を付けることで回避可能です。したがって、

__extension__ typedef long long          int64_t;
__extension__ typedef unsigned long long uint64_t;

とすることで解決します。

次に、最小幅を指定した整数型です。それらの型は、int_leastN_t の形式をしていますが、今回の場合、各整数型は全て 8×2n ビットですので、対応する intN_t に定義すればよさそうです。実例として8ビットのものだけを挙げますが、他も同様です。

typedef int8_t  int_least8_t;
typedef uint8_t uint_least8_t;

今度は、指定幅以上を持つ最も高速な整数型です。規格では、「通常、最も早く処理できる」型となっていますが、通常とはどんな場合かについて書かれていません。"h8300-hms"ターゲットの場合、汎整数拡張のコストが結構大きいので、ここでは、int型未満のものはint型として扱う方針で定義したいと思います。

typedef int           int_fast8_t;
typedef unsigned int  uint_fast8_t;
typedef int           int_fast16_t;
typedef unsigned int  uint_fast16_t;
typedef long          int_fast32_t;
typedef unsigned long uint_fast32_t;
__extension__ typedef long long          int_fast64_t;
__extension__ typedef unsigned long long uint_fast64_t;

こんな感じです。

次は、最大幅を持つ整数型です。これはlong long型ですので、

__extension__ typedef long long          intmax_t;
__extension__ typedef unsigned long long uintmax_t;

となります。

最後は、オブジェクトを指すポインタの値を保持できる整数型です。ポインタのサイズは、H8/300およびH8/300HとH8Sのノーマルモードが16ビットで、それ以外は32ビットですから、

#if defined(__H8300__) || defined(__NORMAL_MODE__)
typedef int           intptr_t;
typedef unsigned int  uintptr_t;
#else
typedef long          intptr_t;
typedef unsigned long uintptr_t;
#endif

とすることができます。

各種マクロの定義については次回に回したいと思います。

一部間違いがありましたので訂正しました。
typedef __extension__ long long int64_t;
ではなく、
__extension__ typedef long long int64_t;
が正解です。__extension__を用いた他の型定義も同様です。
2006/01/26 22:57|処理系の特性TB:0CM:0

 

今回は、浮動小数点数の特性を表すマクロを定義する<float.h>についてお話します。"h8300-hms"ターゲットの場合、double型もlong double型も、すべてfloat型と同じ精度しか持ちませんので、DBL_〜マクロもLDBL_〜マクロも、定義すべき値は、対応するFLT_〜マクロと同じになります。ただし、正しい型として評価されるように、FLT_〜マクロには 'F' を、LDBL_〜マクロには 'L' を添え字として付ける必要がありそうです。

と、このように考えていたのですが、gccでは、それらのマクロを定義するために、"__"を前後に付けたマクロが予め定義されているようです。例えば、FLT_MAXの場合、それに対応する__FLT_MAX__が予め定義されているようなのです。

これで、<float.h>の実装は非常に簡単になります。

#define FLT_DIG         __FLT_DIG__
#define FLT_EPSILON     __FLT_EPSILON__
#define FLT_MANT_DIG    __FLT_MANT_DIG__
#define FLT_MAX_10_EXP  __FLT_MAX_10_EXP__
#define FLT_MIN_10_EXP  __FLT_MIN_10_EXP__
#define FLT_MAX_EXP     __FLT_MAX_EXP__
#define FLT_MIN_EXP     __FLT_MIN_EXP__
#define FLT_MAX         __FLT_MAX__
#define FLT_MIN         __FLT_MIN__

それぞれのマクロの意味については、(C++用ではありますが)こちらを参照してください。DBL_〜マクロやLDBL_〜マクロについても、上記の同じように定義できますので、ここでは省略します。

他にもいくつかのマクロを定義する必要があります。

#define FLT_RADIX       __FLT_RADIX__
#define FLT_EVAL_METHOD __FLT_EVAL_METHOD__
#define DECIMAL_DIG     __DECIMAL_DIG__

このうち、FLT_EVAL_METHODとDECIMAL_DIGは、C99から新たに導入されたマクロです。<limits.h>のLLONG_MAXなどもそうだったのですが、C99より前のC言語では、これらのマクロをヘッダ内で定義するのは厳密には規格に反しているのですが、機能を制約して厳密に規格合致させるより、そのまま提供した方が便利なので、そうしています。__STRICT_ANSI__マクロを調べて場合分けしてもよいのですが、とりあえずはやめておきます。

FLT_EVAL_METHODは、浮動小数点の演算をどのような範囲と精度で行うかを表すもので、以下のような値をとります。

-1   不定
 0   演算や定数の評価を、それぞれの型の範囲・精度で行う。
 1   floatとdoubleはdoubleの、long doubleはlong doubleの範囲・精度で行う。
その他 上記以外の負の値は処理系定義。

DECIMAL_DIGは、処理系がサポートする最も大きい浮動小数点型の値を、いったん n 桁の10進数にしてから、また元に戻したときに、情報が失われない最小の n を表します。

もう一つだけマクロを定義しなければなりません。それはFLT_ROUNDSです。これは、gccの浮動小数点演算用のサブルーチンの仕様に依存しますが、もっとも近い値となる方向へ丸めるようなので 1 に定義します。

#define FLT_ROUNDS   1

これで、<float.h>の内容は一通り網羅できたと思います。
2006/01/25 12:02|処理系の特性TB:0CM:0

 

今回は、標準Cライブラリにおける共通定義を行う<stddef.h>についてお話します。<stddef.h>は、標準Cライブラリのヘッダの中でも、最も処理系に依存するものです。さらに、C++にも対応するとなると、いろいろ面倒なことをやらなければなりません。

まずは、size_t型からです。size_t型はsizeof演算子の結果の型ですので、意味的には、gcc拡張である__typeof__を使って、

typedef __typeof__(sizeof(int)) size_t;

とするのが妥当かと思います。ただ、__SIZE_TYPE__というマクロが予め定義されていますので、今回は、

typedef __SIZE_TYPE__ size_t;

としたいと思います。なお、size_t型は、<stddef.h>以外のいくつかのヘッダでも定義されていますので、次のように二重定義のガードを行います。

#ifndef _SIZE_T
#define _SIZE_T
typedef __SIZE_TYPE__ size_t;
#endif

次に、ポインタどうしの減算の結果の型であるptrdiff_t型ですが、__SIZE_TYPE__と同様に、ptrdiff_t型のために__PTRDIFF_TYPE__というマクロも予め定義されているようですので、

typedef __PTRDIFF_TYPE__ ptrdiff_t;

とします。ptrdiff_tは<stddef.h>のみで定義されるので、二重定義のガードは必要ないでしょう。

同じく、ワイド文字を表す型であるwchar_t型も、__WCHAR_TYPE__マクロが予め定義されていますので、

#if defined(_WCHAR_T) && !defined(__cplusplus)
#define _WCHAR_T
typedef __WCHAR_TYPE__ wchar_t;
#endif

のように書くことができます。wchar_tは、C++では予約語ですので、__cplusplusマクロを用いて、C++のときは定義しないようにしています。

次は、空ポインタ定数を表すNULLです。NULLは、CとC++では定義を変える必要があります。C言語では、void*型から他の型へのポインタに暗黙の変換ができますは、C++では明示的なキャストが必要なため、

#define NULL ((void*)0)

とすることができません。通常、C++ではNULLを 0 または 0L に定義しますが、gccではNULLを定義するために__nullというマジック値が提供されています。したがって、

#ifndef __cplsuplus
#define NULL ((void*)0)
#else
#define NULL __null
#endif

とすることで、CおよびC++の両方に対応することができます。

さて、最後はoffsetofです。このマクロは構造体や共用体のメンバの先頭からのオフセットを調べるためのものです。これも、CとC++では、定義を変更する必要があります。
Cでは、

#define offsetof(type, member) ((size_t)&((type*)0)->member)

と書けば問題ありません。しかし、C++では、&演算子が多重定義されていると期待通りに動作しません(C互換型でも、非メンバ関数として多重定義できます)。したがって、ちょっとした工夫が必要になります。

#define offsetof(type, member) \
        ( __offsetof__ ( reinterpret_cast<std::size_t> \
            (&reinterpret_cast<const volatile char*&> \
                (static_cast<type*>(0)->member) ) ) )

のようにしなければなりません。ここで__offsetof__は、それに続く括弧内に、本来であれば整数定数式に含まれてはならない構文要素があった場合でも、整数定数式とすることができるgccの拡張機能です。いったん、const volatile char&型にキャストすることで、例え、&演算子が多重定義されていたとしても、無視することができます。また、size_t型はstd::size_t型としています。

▽続きを読む▽
2006/01/24 19:45|処理系の特性TB:0CM:0

 

今回は、処理系の特性を把握する目的を兼ねて、<limits.h>の実装についてお話します。<limits.h>は、主として、整数型の限界値を表すマクロを定義するためのものです。それぞれのマクロは、#if指令で使用するのに適した形式でなければなりません。すなわち、マクロの定義内容に、キャストやsizeofといった演算子を使うことはできません。

それでは、MB_LEN_MAXからです。MB_LEN_MXマクロは、処理系がサポートしている最大の多バイト文字のバイト数を表します。gccではUTF-8がサポートされていますので、本来であれば6に設定すべきですが、UCS-2の範囲で十分なので3とします。なお、gccはISO-2022-JPもサポートしていますが、シフトシーケンスに依存するエンコーディングは、利用価値が低い上に実装も面倒なので非サポートとします。というわけで、

#define MB_LEN_MAX 3

ということにします。3あれば、シフトJISやEUCを扱う上でも十分です。

次に、CHAR_BITですが、これはchar型を構成するビット数を表します。C言語では、char型のサイズが1バイトですので、sizeof演算子で得られる値も、このchar型のサイズが基準となります。"h8300-hms"ターゲットのgccでは、char型は8ビットですので、CHAR_BITは8に定義します。

#define CHAR_BIT 8

さて、いよいよ整数型の最大最小値に入ります。まずは、signed char型の最大最小値を表すSCHAR_MAXとSCHA_MINです。signed char型もchar型同様8ビットであり、負の値は2の補数を使って表現しますので、

#define SCHAR_MAX 127
#define SCHAR_MIN (-128)

となります。SCHAR_MINの定義は括弧でくくっておかないと、1-SCHAR_MINのように記述したとき、コンパイルオプションによっては、1--128のように展開されるので、"--"が減分演算子だとコンパイラが誤認してしまいます。

続いて、unsigned char型の最大値を表すUCHAR_MAXです。符号なし整数型の最小値は 0 に決まっているので、マクロはありません。unsigned char型も8ビットですので、UCHAR_MAXは255になります。

#define UCHAR_MAX 255

次は、char型の最大最小値を表すCHAR_MAXとCHAR_MINですが、char型は、コンパイルオプションによって、符号付きにも符号なしにもなりますから、どんなコンパイルオプションが指定されたかを知る必要があります。gccの場合、char型が符号なしの場合には、__CHAR_UNSIGNED__というマクロが予め定義されます。#ifdefまたは#ifndef指令を使って、判別することができそうです。

#ifndef __CHAR_UNSIGNED__
#define CHAR_MAX SCHAR_MAX
#define CHAR_MIN SCHAR_MIN
#else
#define CHAR_MAX UCHAR_MAX
#define CHAR_MIN 0
#endif

小さい型から順に解説していってますので、次はshort型の番です。"h8300-hms"ターゲットのgccでは、short型は16ビットです。ただし、int型が16ビットか32ビットかはコンパイルオプションに依存します。short型の話をしているのに、なぜint型が関係あるのかと思われるかもしれませんが、実は大ありなのです。

<limits.h>で定義すべき最大最小値のマクロは、その型のオブジェクトのみで構成される式が汎整数拡張されたときの型と同じ型に評価されなければなりません。例えば、SHRT_MINを安易に(-32768)と定義してしまうと、int型が16ビットの場合、32768はint型の範囲を超えているので、long型になってしまいます。long型の定数に符号演算子 - を付けてもlong型のままです。これでは規格に合致しません。

というわけで、SHRT_MAXおよびSHRT_MINは次のように定義します。

#define SHRT_MAX 32767
#define SHRT_MIN (-32767-1)

(-32767-1)とすることで、例えint型が16ビットでも32767はint型の範囲におさまりますからint型になります。1もint型ですので、int型どうしの演算もやはりint型になります。

同様の理由で、unsigned char型の最大値であるUSHRT_MAXも65535とすることはできません。そこで、

#define USHRT_MAX 0xffff

とします。10進定数は、int型の範囲を超えるとlong型になりますが、8進または16進定数は、int型の範囲を超えると、まずunsigned int型になります。したがって0xffffは、int型が16ビットであればunsigned int型に、int型が32ビットであればint型に評価されることになります。

ところで、unsigned short型が汎整数拡張された場合ですが、int > shortの場合はunsigned int型ではなくint型になります。汎整数昇格が起きた場合、値は保存されますが、型の符号の有無は保存されないのです。

続いてint型ですが、これもchar型と同じく、コンパイルオプションに依存しますので、普通なら場合分けが必要です。しかし、"h8300-hms"ターゲットのgccでは、__INT_MAX__というマクロが予め定義されます。これを利用すると、

#define INT_MAX __INT_MAX__
#define INT_MIN (-__INT_MAX__-1)

と書くことができます。

unsigned int型の最大値を表すUINT_MAXにも__INT_MAX__を使うことができそうです。ただし、最終的な型をunsigned int型にする仕組みも必要です。

#define UINT_MAX (__INT_MAX__*2U+1)

簡単に解説すると、__INT_MAX__が32767の場合、2倍することで、65534になります。ここで、単なる 2 ではなく、2U を掛けているので、その時点でunsigned int型になります。そして、65534U+1で65535Uを作っています。

long型とlong long型についても、__LONG_MAX__および__LONG_LONG_MAX__というマクロが予め定義されていますので、これらを使って、

#define LONG_MAX __LONG_MAX__
#define LONG_MIN (-__LONG_MAX__-1)
#define ULONG_MAX (__LONG_MAX__*2UL+1)

#define LLONG_MAX __LONG_LONG_MAX__
#define LLONG_MIN (-__LONG_LONG_MAX__-1)
#define ULLONG_MAX (__LONG_LONG_MAX__*2ULL+1)

と書くことができます。
2006/01/24 14:48|処理系の特性TB:0CM:4

ホーム 全記事一覧 次ページ >>

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

このブログをリンクに追加する

Powered By FC2ブログ 

Powered By FC2ブログ
ブログやるならFC2ブログ

Copyright(C) 2006 TAKAGI Nobuhisa All rights reserved.
Powered by FC2ブログ. 無料ホームページ アフィリエイト レンタルサーバー FC2ブログ 一戸建て template designed by 遥かなるわらしべ長者への挑戦.