Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[C言語]ISO/IEC9899から整数型の"正しい定義"を知る

Posted at

はじめに

今回はISO/IEC9899というC言語の規格を記している資料から型の正しい定義について学んでみる

なぜ今さら型について学ぶのか

移植性のあるコードを書くためです。

「long型が実行環境によって32bitなのか64bitなのか変わる」という話は聞いたことがあると思います。ただ僕はこの話を聞いてから「それぞれの型の"正しい定義"を知らないと移植性のあるコードは書けない」と、前々から感じていました。
なぜなら環境によってその"正しい定義"は守られているはずだからです。
逆にいえばそれ以外はその環境が自由に決めていると考えた方が良いのかもしれません。

型は以下のように定義されています

(原文)
Types are defined in the following categories:
— integer types having certain exact widths;
— integer types having at least certain specified widths;
— fastest integer types having at least certain specified widths;
— integer types wide enough to hold pointers to objects;
— integer types having greatest width.

(和訳)
型は以下のカテゴリで定義されます。
ー ある確かな幅を持つ整数型
ー 少なくとも確かな特定の幅を持つ整数型
ー 少なくとも確かな特定の幅を持つ最速の整数型
ー オブジェクトへのポインタを保持するのに十分な幅を持つ整数型
ー 最大の幅を持つ整数型

なんとなくC言語を学んできた人であれば納得できると思います。
ここから実際に僕らがよく使う整数型の定義を見ていきます。

整数型

bool型

数値型と言って最初にbool型を紹介するのは違和感があるかもしれません。ですが、bool型はfalse(0)とtrue(0以外)を扱う型なので整数型に属します。
そしてbool型は

「0と1を格納するのに十分な大きさを持つ整数型」

と定義されています。
なので環境によっては0か1かしか表さないことがあるということです。

char型

char型は文字型だ、という意見があると思います。ですがchar型は数値を格納してそれに対応する文字を出力することもできるので文字型でありながら数値型に属しています。
char型は

「基本実行文字セットのメンバーを格納するのに十分な大きさを持つ整数型」

と定義されています。
ここでいう基本文字セットはASCIIからきています。

int型

int型は

「ヘッダで定義されているINT_MINからINT_MAXを格納するのに十分な、その実行環境で自然とされている大きさを持つ整数型」

と定義されています。
なので規格上ではint型はbit数が決まっていません。
しかし、limit.hについて
INT_MIN <= -(2^15 - 1) <= (2^15 - 1) <= INT_MAX
と定義されているのでint型は少なくとも16bit以上であることが保証されています。

long型、long long型

long = long int
long long = long long int
上記のように省略した形です。

これらについて定義はint型と同様です。
そしてlimits.hについて
LONG_MIN <= -(2^31 - 1) <= (2^31 - 1) <= LONG_MAX
LLONG_MIN <= -(2^63 - 1) <= (2^63 - 1) <= LLONG_MAX
と定義されているので、long型は32bit以上、long long型は64bit以上であることが保証されています。

signed、unsignedについて

それぞれ符号あり(signed)、符号なし(unsigned)の整数型を宣言するための修飾子です。
signedは付けなくてももともと整数型は符号ありなので変わりませんが、unsignedを付けると符号分の1bitを多く数値を表すのに使えるので表せる数値の絶対値の大きさが1bit分増えます。
INT_MAX -> 2^15 - 1
UINT_MAX -> 2^16 - 1

char型のみ特殊で

「char型はsigned char型またはunsigned char型のいずれかと同じ範囲で定義しなければなりません」

と書いてあります。つまりchar型だけはsigned char型と定義してあげないと符号なしの可能性があります。

また、型の最大値を超える計算について、signedであればオーバーフローが発生し未定義の動作(処理系に依存する)になりますが、unsignedだとその型の最大値+1を法として剰余演算が行われるのでオーバーフローが発生しません。
UINT_MAX -> 2^16 - 1 = 65535
65535 + 10 = (65535) ≡ 9(mod65536)

定義を知った上で...

型について知った上で実際にコードを書く時に意識すべきことは

文字型であるchar型を除いて
整数型はintN_tやuintN_tで表記すべき

ということだと思います。

また、char型はsigned char型で定義すべきです。

規格でも定められていますが、Nbitの整数型をintN_tと表記することができます。
なので32bitの整数型が欲しいときはint32_tと宣言してあげるのがより良いコードであると言えます。
また、それに伴うINTN_MAXも定義されています。

終わりに

小数型についても調べてみると面白そうです。
参考文献:https://www.open-std.org/jtc1/sc22/wg14/www/standards

0
0
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

@Canard_engineer_c_cpp's pickup articles

Canard_engineer_c_cpp

@Canard_engineer_c_cpp

国立大学在学生 42tokyo本科生 コンピュータサイエンス、C/C++を主に勉強中で42生にはもちろん、そうでない方にも役立つ記事を書きます。
Linked from these articles

Comments

GrasshopperX
@GrasshopperX(草士)

移植性のあるコードを書くためです。
...
整数型はintN_tやuintN_tで表記すべき

intN_t/uintN_tはオプションなので、(規格的には)移植性があるとは言えないけどね( *´艸`)

0
Canard_engineer_c_cpp
@Canard_engineer_c_cpp

そうですね!移植性というのは語弊があったかもしれないです。
自分の意図としては「intN_t型であればその処理系に存在するのならbit数がN、ないのならコンパイルエラーになるはずなので意図しない挙動が起こらない」ということです、ご指摘ありがとうございます!

0
SaitoAtsushi
@SaitoAtsushi(齊藤 敦志)

intN_t で整数型を表すようにしても気づくのが難しい移植性の問題が発生する事例として以下のようなものを見たことが有ります。

uint8_t bytes[] = { 0x89, 0xAA };
uint32_t number;

number = bytes[0] << 8 | bytes[1];

バイト列を結合してひとつの整数を作るというもので、この場合は 0x89aa を作ることが意図されているのですが int が 16bit の環境では意図通りになりません。 プログラムの字面の上では int が現れないので気づき難いようです。

もちろん基本的には整数型を intN_t で表すのは良い習慣だとは思いますが、 それだけではなくなるべく言語仕様を広く把握して慎重にやるしか仕方がないですね。

0
Canard_engineer_c_cpp
@Canard_engineer_c_cpp

確かに気をつけるべきは型名だけではないですね…
実際に規格を読んで開発している人はあまり見ないですが、自分は今回の記事が規格を読むきっかけになったので型以外の部分も読んでみようと思います!
コメントありがとうございます!

0
fujitanozomu
@fujitanozomu(藤田 望)

@Canard_engineer_c_cppさん、

[C言語]ISO/IEC9899から整数型の"正しい定義"を知る

ISO/IEC9899と言ってもANSI C - Wikipediaによると

C90 = ISO/IEC 9899:1990
C95 = ISO/IEC 9899/AMD1:1995
C99 = ISO/IEC 9899:1999
C11 = ISO/IEC 9899:2011
C17 = ISO/IEC 9899:2018

ということみたいですが、この記事が何について言及されてるかは明確にされると良いと思います。

0
fujitanozomu
@fujitanozomu(藤田 望)

bool型

まだ規格として決まっていないC23ではbool型が使えるようになりそうという話は聞いた気がするのですが、それ以前の規格ではboolは型ではなくマクロだったと思います。bool型ではなくC99で決めらた_Bool型のことを言われてるのではないでしょうか。

なので環境によっては0か1かしか表さないことがあるということです。

環境によっては標準規格で決められた_Bool型で0か1以外の値を表せられるということでしょうか?

0
fujitanozomu
@fujitanozomu(藤田 望)

char型

ここでいう基本文字セットはASCIIからきています。

ASCII以外の文字セットは規格外ということですか?

0
fujitanozomu
@fujitanozomu(藤田 望)

また、char型はsigned char型で定義すべきです。

なんでですか?

0
Canard_engineer_c_cpp
@Canard_engineer_c_cpp

今回参考にした規格はC99です、明記すべきでしたね

boolは_Boolのエイリアス(別名)なので混乱を避けるために触れませんでした(こちらも明記すべき)
また、環境によっては0か1以外の値を格納できます。

ASCII以外の文字セットは規格では明記されていませんが、各環境によって拡張文字セットとして採用されていることがあります。

記事本文にも書きましたが、char型だけはsignedと明記しないと符号付きであることが保証されないからです。

0
fujitanozomu
@fujitanozomu(藤田 望)

@Canard_engineer_c_cppさん、

今回参考にした規格はC99です、明記すべきでしたね

boolは_Boolのエイリアス(別名)なので混乱を避けるために触れませんでした(こちらも明記すべき)

投稿後も記事は編集できるので、後から気付いた点があれば記事は修正されると良いでしょう。

また、環境によっては0か1以外の値を格納できます。

ISO/IEC 9899:1999を基礎としているX 3010:2003 (ISO/IEC 9899:1999)には

6.3.1.2 論理型 任意のスカラ値を_Bool型に変換する場合,その値が0に等しい場合は結果は0とし,それ以外の場合は1とする。

とあるので_Bool型のオブジェクトへは0か1以外格納できないと思います。

#include <stdio.h>

int main(void)
{
    char c = 2;
    _Bool b;

    b = c;
    printf("b = %d\n", b);
}
実行結果
b = 1

あるいは、こんな例を想定されているでしょうか?

#include <stdio.h>
#include <string.h>

int main(void)
{
    char c = 2;
    _Bool b;

    memcpy(&b, &c, 1);
    printf("b = %d\n", b);
}
gccでコンパイル、実行結果
b = 2

これはX 3010:2003 (ISO/IEC 9899:1999)の6.2.6.1に

オブジェクト表現の中には,そのオブジェクト型の値を表現しないものがある。オブジェクトに格納した値がオブジェクト型の値を表現せず,文字型ではない左辺値式でそれを読み取る場合,その動作は未定義とする。

に該当し未定義動作となるので_Bool型のオブジェクトへはやはり0か1以外格納できないと思います。

ASCII以外の文字セットは規格では明記されていませんが、各環境によって拡張文字セットとして採用されていることがあります。

規格の話をされてるところで機種に依存した話を持ち出される意味はないと思います。

X 3010:2003 (ISO/IEC 9899:1999)では5.2.1に

ソース基本文字集合及び実行基本文字集合は,少なくとも次に掲げる要素をもっていなければならない。
− 26個のラテンアルファベットの大文字(uppercase letter)
A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z
− 26個のラテンアルファベットの小文字(lowercase letter)
a b c d e f g h i j k l m
n o p q r s t u v w x y z
− 10個の10進数字(digit)
0 1 2 3 4 5 6 7 8 9

ソース基本文字集合及び実行基本文字集合の双方において,10進数字に関する上の並びにおいて,0の右側に並んでいる各文字の値は,一つ左側にある文字の値に比べ1だけ大きくなければならない。

と説明があり、数字は文字コードが連続して並んでいることを規定していますがラテンアルファベットの大文字と小文字については文字コードが連続して並んでいることを規定してしていません。
規格がASCIIを想定していればラテンアルファベットも数字同様に文字コードが連続して並んでいることを規定できる筈ですがそうなってはいないということです。つまりはC言語の標準規格ではASCIIを規定しておらず、規格に準拠したC言語のプログラムであればASCIIを想定するのは間違いとなります。

記事本文にも書きましたが、char型だけはsignedと明記しないと符号付きであることが保証されないからです。

C言語ではintsigned intは同じ型ですが、charsigned charunsigned charはそれぞれ別の型です。標準ライブラリの引数の型と合わせるためにcharを使用する機会は普通にあるでしょう。符号付き、あるいは符号なしの小さい整数を使用したい場合にsigned charunsigned charを使う場合もあると思います。これらは必要に応じて使い分けるべきものであり、charは無条件でsigned charへ置き換えるようなことではないと思います。

0
Canard_engineer_c_cpp
@Canard_engineer_c_cpp

_Bool型に1以外の値を代入して変換されることと、値の格納が“可能“かどうかというのは別の話ですね。

ASCIIは規定されているというより対応しているということですね。規定外の話をしても意味が無いというのは違います。今回は規定外の挙動が存在することを意識することでより規定に添うことが趣旨にあるので。

もちろん無条件でsigned charにすべきとは書いていません。確かに、“基本的に“なども書いていませんが。その辺りはこの記事を読んで頂いた方がどう考えて応用するかの部分です。

0

Let's comment your feelings that are more than good

Qiita Conference 2024 Autumn will be held!: 11/14(Thu) - 11/15(Fri)

Qiita Conference is the largest tech conference in Qiita!

Keynote Speaker

Takahiro Anno, Masaki Fujimoto, Yukihiro Matsumoto(Matz)

View event details

Being held Article posting campaign

0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Login to continue?

Login or Sign up with social account

Login or Sign up with your email address