Overview
原典は、https://matt.sh/howto-c (日本語訳を作ってくださっている方のページは http://postd.cc/how-to-c-in-2016-1/ )を、分析してみるシリーズです。
「2016年、C言語はどう書くべきか」をちょっと分析してみる (building編) では、リンク時の最適化 option (--flto)についての分析でした。
今回は、Writing code編です。
Types
#include <stdint.h>
で、定義された型を使いましょうとのこと。
- http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdint.h.html
-
http://www.c-lang.org/detail/stdint_h.html
に参考文献があります。
c-langの説明から引用しますと、
名称 | 内容 |
---|---|
intN_t | 幅がNビット、詰め物ビットなし、2の補数で表現される、符号付き整数型。例えば、int8_tなら、厳密に幅が8ビットの符号付き整数型となる。 |
uintN_t | 幅がNビットの符号なし整数型。 |
int_leastN_t | 少なくとも幅がNビットの符号付き整数型。 |
uint_leastN_t | 少なくとも幅がNビットの符号なし整数型。 |
int_fastN_t | 少なくとも幅がNビットの最速符号付き整数型。 |
uint_fastN_t | 少なくとも幅がNビットの最速符号なし整数型。 |
intptr_t | voidへのポインタがこの型へ変換可能でかつ逆の変換も可能な符号付き整数型。 |
uintptr_t | voidへのポインタがこの型へ変換可能でかつ逆の変換も可能な符号なし整数型。 |
intmax_t | すべての符号付き整数型のすべての値を表現可能な符号付き整数型。 |
uintmax_t | すべての符号なし整数型のすべての値を表現可能な符号なし整数 |
ポイント1:
- charとか使うな。
- 処理系によって変わるintとか使うな
- byteは、uint8_t使え
ポイント2:
- int_fast8_tとuint_fast8_tは、8bit幅とは限らず、8bit保持できる最速の方になる (platform依存)
- int_leastN_tとuint_leastN_tは、"Least types provide you with the most compact number of bits for the type you request." つまり、N bitの幅の通りになる、のが普通の実装だそうです。
One thing to be aware of for fast types: it can impact certain test cases.
なお、fastと使うときは、テストケースにインパクトが有ることに注意しましょう、とのこと。
(実際にはplatformによって、幅が違うから。)
intにすべきか、そうでないか
The ISO/IEC 9899:1990 standard specified that the language should support four signed and unsigned integer data types- char, short, int, and long- but placed very little requirement on their size other than that int and short be at least 16 bits and long be at least as long as int and not smaller than 32 bits. For 16-bit systems, most implementations assigned 8, 16, 16, and 32 bits to char, short, int, and long, respectively. For 32-bit systems, the common practice has been to assign 8, 16, 32, and 32 bits to these types. This difference in int size can create some problems for users who migrate from one system to another which assigns different sizes to integer types, because the ISO C standard integer promotion rule can produce silent changes unexpectedly. The need for defining an extended integer type increased with the introduction of 64-bit systems.
ISO/IEC 9899:1990 standardでは、言語は4つのsignedとunsignedのinteger data typesであるchar, short, int, longをサポートすべきとしています。しかし、それらのサイズに関しては、ほんの少ししかrequirementを定めていません。
-
intとshortは少なくとも16bit
-
longは少なくともintと同じサイズで、32bitよりも短くはしない
-
16 bit systemでは、多くの実装が、{char, short, int, long}={8,16,16,32}です。
-
32 bit systemでは、多くの実装が、{char, short, int, long}={8,16,32,32}です。
このint sizeの差は、あるシステムから別のシステムへmigrateしたいユーザーに、いくつかの問題を引き起こします。とりわけ、intのサイズが異るシステムに。というのは、ISO C標準のintegerのpromotion ruleでは、静かに予期せぬ変化を起こすためです。64bit systemの導入に伴い、拡張integerを定義する必要が増えています。
例外としてcharを使うケース
- 既存の(charが既に使われている)APIを使うとき
- read-onlyの初期化を行うとき
const char *hello = "hello";
(というのは、Cのtype of stringのリテラル("hello")は char []
だからです)
ちなみにC11では、unicodeをサポートしていて、utf-8 stringのリテラルは同じくchar []
とのこと。
const char *abcgrr = u8"abc😬";
のように、u8""でくくることで使える (余談: かつてのVC使いは、_T("")を思いだします。。。)
例外としてint, longなどを使うケース
If you are using a function with native return types or native parameters, use types as described by the function prototype or API specification.
nativeな戻り型や、nativeなparameterと共に関数を使う場合は、function prototypeやAPIの規定に従って型を使います。
符号について
unsigned long long int
なんて使わずに、 uint64_t
を使おうということだそうです。同意。。。
pointerの取り扱いについて
longなどに入れてpointerを計算するのではなく、
-
<stdint.h>
で定義されたuintptr_t
や -
stddef.hで定義された
ptrdiff_t
を使うこと、とのことです。
ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew;
とか
printf("%p is unaligned by %" PRIuPTR " bytes.\n", (void *)p, ((uintptr_t)somePtr & (sizeof(void *) - 1)));
みたいに。
ptrdiff_t とは
ptrdiff_t
Signed integral type of the result of subtracting two pointers.
のとおり、signedの整数の型であり、2つのポインターの差の結果を格納するための型とのこと。
system依存のある型
systemに応じて、32bit/64bitのように扱いたい時 (例えばポインタとか)は、longを使いたくなるかもしれないが、使うなということです。
代わりには、pointerの計算のところで書いた ptrdiff_t
でpointerのoffsetを扱ったり、intptr_t
を使えとのこと (uintptr_t
のflavorから来ているとのこと)
ちなみに、 wikipedia のLP64の項目などを見るとわかりますが、platformによってかなり挙動が違いますので、こういうbest practiceには従ったほうが良いです。