C言語のデータ型を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
データ型とは
データ型とは,データを表現する方法のことを言います.
あなたがこれまでにC言語で変数を定義した時,intという予約語を利用しましたよね.
このintとはintegerの略で整数という意味です.
C言語では,整数の他にも様々なデータ型を扱うことができます.
それでは,なぜデータ型というものが必要なのでしょうか.
コンピュータの内部では,値は2進数で表現されています.
例えば,10進数で609という整数値は16ビットの2進数では以下のようになります.
1 |
0000 0010 0110 0001 |
後ろの8ビット(0110 0001)だけを見ると,16進数では0x61,10進数で97,文字(アスキーコード)としてみると’a’になります.
このようにデータの解釈により値が異なります.
したがって,コンピュータ内部では2進数のビット列に,どのような意味をもたせるかということをデータ型で区別します.
C言語には,下表の4つの基本データ型がありますので,それぞれ解説していきます.
データ型 | 意味 |
---|---|
char | 文字型 |
int | 整数型 |
float | 単精度浮動小数点型 |
double | 倍精度浮動小数点型 |
char型(文字型)
char型は,文字型とも呼ばれ,1文字を保持することができるデータ型です.
char型のサイズが1バイトであると定義されています.
ほとんどの処理系では,8ビットの大きさを持っています .
※Digital Signal Processors(DSPs)等には,1バイトが8ビットでないものが存在します.
ちなみに,sizeof演算子で得られるバイト数もchar型の何倍かという意味になっています.
つまり,char型のサイズが,C言語で取り扱うデータの基本単位(バイト)になります.
したがって,厳密にはchar型のサイズは8ビットであると決まってはいないのですが,ほぼ全ての処理系で8ビットです.
なので,一般的な処理系では,char型は1バイト(8ビット)の文字型とみなすことができます.
1バイトが8ビットでないDSPsを知りたいあなたは,sizeof演算子の使い方の記事を読みましょう.
下表に示すchar型の修飾子(signed,unsigned)を利用して,符号ありと符号なしを区別して記述できます.
単にcharと定義すると,符号ありか符号なしになるかは処理系依存となります.
したがって,移植性を考えると,修飾子付きで定義した方が無難です.
データ型 | 意味 |
---|---|
signed char | 符号付きchar型:-128~127 |
unsigned char | 符号なしchar型:0~255 |
char | char型:処理系依存 |
int型(整数型)
int型は,もともとCPUのレジスタの自然長の大きさを持つデータ型として定義されていました.
int型のサイズは通常は4バイト(32ビット)になります.
現在のCPUのレジスタは8バイト(64ビット)が主流なので,レジスタの自然長にはなりません.
int型は下表で示す修飾子を付けることで,意味の異なる様々な型を表すことができます.
符号に関しては,デフォルトでは(何も付けない場合)符号あり(signed)になります.
データ型 | 意味 |
---|---|
signed | 符号あり |
unsigned | 符号なし |
short | 短い整数 |
long | 長い整数 |
long long | 非常に長い整数 |
代表的なデータ型のサイズを下表に示します.
現在,64ビットマシン(CPUとOSを含む)がメインですが,多くの処理系では過去のコードの移植性を考慮して,64ビットOSにおけるint型はもともとの定義の8バイト(64ビット)ではなく,4バイト(32ビット)になっています.
また,64ビットマシンを利用していても,OSが32ビットである場合は,32ビットOSの欄を参照して下さい.
C言語ではデータ型のサイズは厳密には決められておらず,型のサイズ(バイト)はsizeof演算子で取得します.
同様に,データ型の最小値と最大値の定義はlimits.hやfloat.hをインクルードして参照します.
多くの処理系では,下表のようになっています.
移植性を考慮しなければならない場合は,sizeof演算子やlimits.hやfloat.h内の定義を参照して下さい.
データ型 | 32ビットOSでのサイズ(バイト) | 64ビットOSでのサイズ(バイト) |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
long | 4 | 8 |
long long | 8 | 8 |
limits.hやfloat.h内の定義を知りたいあなたはこちらからどうぞ.
int型は,以下で示すような任意の組み合わせで定義が可能です.
具体的には,3つのグループの少なくとも1つあればよく,任意の組み合わせと省略が可能です.
$$
\begin{bmatrix}
signed \\
unsigned \\
\end{bmatrix}
\begin{pmatrix}
short \\
long \\
long\ long \\
\end{pmatrix}
\begin{vmatrix}
int \\
\end{vmatrix}
$$
32ビットOSの場合,以下は同じ意味になります.
- signed long int
- signed long
- signed int
- signed
64ビットOSの場合,以下は同じ意味になります.
- unsigned long int
- unsigned long
float/double型(浮動小数点型)
実数を扱うためには,下表のfloat/double/long double型(浮動小数点型)を利用します.
float型は精度よりもメモリ効率を優先したい場合(例えば大きな配列を確保する際にdouble型ではサイズが大きすぎて領域を確保できない場合等)に利用します.
また,long double型は,double型よりもさらに有効数字と表現可能な範囲が大きくなっています.
処理系によっては,long double型を単にdouble型とみなしてコンパイルするものもあります.
メモリ制約が厳しい環境でなければ,有効数字と丸め誤差を考慮して,double型を利用することが多いです.
データ型 | 名称 | サイズ(バイト) |
---|---|---|
float | 単精度浮動小数点型 | 4 |
double | 倍精度浮動小数点型 | 8 |
long double | 拡張倍精度浮動小数点型 | 8以上(8,10,12,16等) |
私の環境では,long double型のサイズは16バイトでした.あなたの環境でも以下の記事を参考にsizeof演算子を利用して調べてみましょう.
_Bool型(ブーリアン型)
_Boolは,C99規格から採用されたブーリアン型を格納するための型です.
_Boolのサイズは1バイトです.
_Bool型は,stdbool.hでboolというマクロで定義されています.
なので,基本的にはstdbool.hをインクルードしてboolを利用します.
ここで,stdbool.hは/usr/include以下にはなく,/usr/lib/gcc/x86_64-linux-gnu/XX/include/stdbool.hにあります(XXばGCCのバージョン番号).
また,真偽値を表すtrue,falseも定義されています.
変数定義
変数定義とは,変数を入れる箱を自分で用意することです.
変数とは,値(データ)の入れ物(箱)であり,名前が付けられています.
また,変数には,利用した値の種類(整数や実数等)による型があります.
C言語では,全ての変数は利用する前にその型と一緒に定義します.
また,その箱の中身(変数の値)は様々に変更できます.
変数はコードを書くために必要です.
変数を利用しないで複雑なコードを書くことはほぼ不可能です.
この理由として,変数を利用してデータの保存やプログラムの制御を行っているからです.
変数定義は,「型宣言子 変数名の並び」のようにします.
例えば,以下のように変数定義を行います.
1 |
int a, b, c; |
また,この変数定義には,宣言も含まれています.
つまり,これらの変数を定義することが宣言することにもなります.
※変数宣言のみの方法として,extern宣言があります.
extern宣言の実例を知りたいあなたは,関数とはを読みましょう.
変数定義のコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { int a; a = 2 + 3; printf("a = %d\n", a); return 0; } |
実行結果は以下になります.
1 2 3 |
$ gcc variable_definitions.c $ a.out a = 5 |
ここで重要なのが,9行目のint型変数aの定義です.
1 |
int a; |
この行を削除してコンパイルしてみて下さい.
aが宣言されていない(’a’ undeclared)という趣旨のメッセージが出てコンパイルに失敗します.
このように,C言語では変数を利用する時に必ず定義または宣言が必要なことを覚えましょう.
1 2 3 4 5 6 |
$ gcc variable_definitions.c variable_definitions.c: In function 'main': variable_definitions.c:10:3: error: 'a' undeclared (first use in this function) 10 | a = 2 + 3; | ^ variable_definitions.c:10:3: note: each undeclared identifier is reported only once for each function it appears in |
先の例では変数を1つだけ定義しましたが,複数個あるときは以下のように,カンマで区切って並べて書くことができます.
1 |
int a, b, c; |
これは,以下のように書くのと同様です.
1 2 3 |
int a; int b; int c; |
また,以下の書き方もできますので,あなたがコードを理解しやすいように書きましょう.
1 2 |
int a, b; int c; |
変数名のルールと命名規則
C言語の変数名には以下のルールがあります.
- 1文字目には英字(大文字,小文字,アンダースコア_)
- 2文字目には英数字(大文字,小文字,数字,アンダースコア_)
- 大文字,小文字は区別
- 変数名は長さが31文字までは保証
これらのルールを守らないと,コンパイルエラーになります.
※変数名の長さが32文字以上の場合は処理系依存の未定義の動作になります.
これ以外にも慣習的に多くのコーディングスタンダードでは,以下のような変数名の命名規則(付け方)を行うことが多いです.
- 局所変数名はすべて小文字
- 記号定数はすべて大文字
また,この命名規則は,関数名,ラベル名,タグ名等にも当てはまります.
変数や関数名には,意味のある名前を付けましょう.
その変数や関数の意味を端的に表す簡潔な名前をつけるのが良いです.
その際,変数名はローマ字より,英語で付けましょう.
variable_definitions.cで利用している変数名のように,a,b,cのような名前を付けることは基本的には避けるべきです.
ただし,方程式の解の公式のような例外はありますので,興味があるあなたはこちらからどうぞ.
また,ループの制御変数として,i,j,k等を利用することは許容範囲内ですが,tmp0,tmp1,tmp2等の命名はできる限り避けましょう.
ローカル変数は小文字で定義し,グローバル変数は最初の1文字を大文字にして区別するようにしましょう.
#defineを利用した定数はすべて大文字で定義するようにしましょう.
Linux環境でC言語のコードを書く時は,スネークケースのように,変数や定数の単語と単語の間はアンダースコア’_’でつなぐようにしましょう.
他にも変数名をつける記法はありますが,参考までに.
定数
定数は,変数と同様にルールがありますので,そのルールをデータ型別に下表にまとめます.
2進数リテラルはGCC/Clangの拡張機能として実装されていますが,C23規格で正式採用の予定です.
C++14で2進数リテラルが導入されたことが影響している可能性があります.
定数のデータ型 | ルールと例 |
---|---|
int型(整数型)定数 | unsigned intの場合は最後にUまたはuが付きます. 16進数:最初に0x(または0X)が付きます.(例:0x61,0x7fff,0xdeadbeef) 8進数:最初に0が付きます.(例:05,0123U) 2進数:最初に0b(または0B)が付きます.(例:0b1,0b1010) 10進数:16進数,8進数,2進数以外の整数です.(例:1003,123u,-4567) |
long型定数 | long型定数の最後にLまたはlが付きます. Unsigned long型の場合は最後にULまたはulが付きます. 例:-123L,0x61L,07l,0x4567ul,0b100100ul |
long long型定数 | 整数定数の最後にLLまたはllが付きます. unsigned long long型の場合は最後にULLまたはullが付きます. 例:-123LL,0x61LL,07ll,0x345ull,0b101010ull |
double型(浮動小数点型)定数 | 小数点,及びEまたはeも利用して指数表現が可能です.(例:123.45,-3.14e-25,6.45E12) ※3.14e-25は3.14*10^-25と同じ意味です. C99からは16進数で表記可能です.先頭に0x,仮数部の後にp,最後に指数部(省略可能).(例:0x1p,0x2p3,0x2.5p4) |
float型定数 | 小数点,及びEまたはeを利用して指数表現が可能です. 浮動小数点数の最後にFまたはfが付きます.例:123.45f,-3.14e-25f,6.45E12F,0x2.5p4f |
long double型定数 | 小数点,及びEまたはeを利用して指数表現が可能です. 浮動小数点数の最後にLまたはlが付きます. 例:123.45l,-3.14e-25l,6.45E8L,0x2.5p5L |
虚数単位 | C99から導入された複素数型で虚数部を定義する時に利用します. 浮動小数点型の最後にIまたはiが付きます. 虚数のみの例:1.2I,3.4i, 複素数の例:1.2+3.4I,5.6+7.8i |
文字定数 | シングルクォーテーション’で挟みます. 例:’a’,’l’,’#’ 特殊な文字(エスケープシーケンス)は,下表のように表現します. |
文字列定数 | ダブルウォーテーション”でくくられた文字の列です. 文字の最後にはNULL文字(’\0’:char型(1バイト)の0)が自動的に入ります. 文字列中のダブルウォーテーションは\”で表現します. |
エスケープシーケンスは下表になります.
意味 | 書き方 |
---|---|
改行 | ’\n’ |
復帰 | ’\r’ |
タブ | ’\t’ |
バックスペース | ’\b’ |
ベル | ’\a’ |
\自身 | ’\\’ |
シングルクォーテーション | ’\’’ |
ダブルウォーテーション | ’\”’ |
8進ビットパターン | ’\ooo’(oooは8進数) |
16進ビットパターン | ’\xhh’(hhは16進数) |
例えば,8進ビットパターンの'\377'(8進数の0377)は,10進数の255,16進ビットパターンの'\xff'(16進数の0xff)と同じです.
こちらのツールで2進数,8進数,10進数,16進数の相互変換ができます.
また,C言語で10進数と2進数,8進数,16進数の相互変換を知りたいあなたはこちらからどうぞ.
各種データ型を利用したコード
各種データ型を利用したコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdbool.h> int main(void) { signed char sc; unsigned char uc; short int si; unsigned short int usi; int i; unsigned int ui; long int l; unsigned long int ul; float f; double d; long double ld; bool b; sc = -123; uc = 254u; si = -32000; usi = 32109u; i = 12345; ui = 65432u; l = 1000000l; ul = 20000000ul; f = 1.0f / 3.0f; d = 1.0 / 3.0; ld = 1.0l / 3.0l; b = true; printf("sc = %d\n", sc); printf("uc = %d\n", uc); printf("si = %d\n", si); printf("usi = %d\n", usi); printf("i = %d\n", i); printf("ui = %u\n", ui); printf("l = %ld\n", l); printf("ul = %lu\n", ul); printf("f = %20.18f\n", f); printf("d = %20.18lf\n", d); printf("ld = %20.18Lf\n", ld); printf("b = %d\n", b); return 0; } |
実行結果は以下になります.
11~13行目のfloat/double/long double型で1.0 / 3.0の計算結果を比較すると,精度の違いがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ gcc data_types.c $ a.out sc = -123 uc = 254 si = -32000 usi = 32109 i = 12345 ui = 65432 l = 1000000 ul = 20000000 f = 0.333333343267440796 d = 0.333333333333333315 ld = 0.333333333333333333 b = 1 |
配列
配列とは,様々な要素が一定の規則により並べられているデータの集合のことです.
例えば,箱が5個あって,その中にそれぞれ本が何冊か入っていたとすると,それは配列で表現できます.
この状況をコードとして書く場合,箱を変数とみなし,箱(変数)に順番に番号を付けていきます.
そして,その番号を利用して個々の箱(変数)を表します.
その番号のことを,配列の添字(そえじ:インデックス)と言います.
配列を利用するコード
配列を利用するコードは以下になります.
5個の変数を入力して配列に格納し,これらの合計値と平均値を計算します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #define MAX 5 int main(void) { double x[MAX]; double sum, avg; int i; for (i = 0; i < MAX; i++) { printf("Please input x[%d]: ", i); scanf("%lf", &x[i]); } sum = 0.0; for (i = 0; i < MAX; i++) { sum = sum + x[i]; } avg = sum / i; printf("sum = %lf\n", sum); printf("avg = %lf\n", avg); return 0; } |
11行目の以下の部分が配列を定義しているところです.
1 |
double x[MAX]; |
#define文でMAXを5と定義しているので,xという名前が付いた5個の要素を持つ配列を定義したことになります.
100個の変数で別々の名前を付けて定義して利用しても間違いではありませんが,コードを書くのが複雑になります.
その点,配列利用すると添字を変更するだけでデータにアクセスできるので,for文等を利用して簡単にコードを書けます.
配列にアクセスするためには,添字を利用して以下のように書きます.
これは,xという配列の0番目の要素に2.3を代入する文です.
ここで,C言語の配列の添字は,必ず0から始まります(0オリジンと言います).
1 |
x[0] = 2.3; |
また,以下のようにxと0の場所を交換して書くことも可能です.(ほとんど利用しませんが...)
1 |
0[x] = 2.3; |
例えば,以下の配列は,配列要素が5つのint型の配列を定義したことになります.
1 |
int y[5]; // y[0], y[1], y[2], y[3], y[4] |
また,配列を定義した時に初期化したい場合は以下のように{}で囲んだ後に各々の添字に格納する要素を書きます.
以下の場合,y[0]に1,y[1]に2,y[2]に3,y[3]に4,y[4]に5を格納します.
1 |
int y[5] = {1, 2, 3, 4, 5}; |
また,C99から導入された指示付きの初期化指定子を利用して以下のように初期化しても同様になります.
1 |
int y2[MAX] = {[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5}; |
以下のように指示付きの初期化指定子を利用しない添字(y[0],y[1],y[3])は0に初期化されます.
1 |
int y3[MAX] = {[2] = 3, [4] = 5}; // y[0] = y[1] = y[3] = 0; |
配列の定義時に初期化するコード
配列の定義時に初期化するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #define MAX 5 int main(void) { int y[MAX] = {1, 2, 3, 4, 5}; int y2[MAX] = {[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5}; int y3[MAX] = {[2] = 3, [4] = 5}; // y[0] = y[1] = y[3] = 0; int i; for (i = 0; i < MAX; i++) { printf("y[%d] = %d\n", i, y[i]); } for (i = 0; i < MAX; i++) { printf("y2[%d] = %d\n", i, y2[i]); } for (i = 0; i < MAX; i++) { printf("y3[%d] = %d\n", i, y3[i]); } return 0; } |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ gcc array2.c $ a.out y[0] = 1 y[1] = 2 y[2] = 3 y[3] = 4 y[4] = 5 y2[0] = 1 y2[1] = 2 y2[2] = 3 y2[3] = 4 y2[4] = 5 y3[0] = 0 y3[1] = 0 y3[2] = 3 y3[3] = 0 y3[4] = 5 |
C99規格から採用された可変長配列を利用するコード
C99規格から可変長配列(VLA:Variable Length Array)が採用されました.
C99以前では配列の定義時に変数を利用できなかったのですが,C99規格からは配列の定義時に変数を利用できます.
可変長配列を利用するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { int i, n; do { printf("Please input a natural number: "); scanf("%d", &n); } while (n <= 0); int x[n]; for (i = 0; i < n; i++) { x[i] = i; printf("x[%d] = %d\n", i, x[i]); } return 0; } |
実行結果は以下になります.
scanf関数で自然数を入力し,その値により配列のサイズが可変長(例:3,5)になっていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ gcc variable_length_array.c $ a.out Please input a natural number: 3 x[0] = 0 x[1] = 1 x[2] = 2 $ a.out Please input a natural number: 5 x[0] = 0 x[1] = 1 x[2] = 2 x[3] = 3 x[4] = 4 |
文字配列
C言語には,文字列型というデータ型は存在しません.
それでは,C言語で文字列は扱えないのかと疑問に思うかもしれませんが,そうではありません.
文字はキャラクタコード(通常はASCIIコード)で表現されるので,1文字は1バイトの数値で表現できます.
※他にもEBCDICコードやUnicode等がありますが,キャラクタコードは一般的にはASCIIコードの事を指します.
したがって,1文字はchar型(1バイトの整数型)の変数として扱うことができます.
それでは,1文字を代入できるchar型の変数codeを定義してみましょう.
1 |
char code; |
この場合はsigned,unsignedのどちらでも構いません.
そして,変数codeにアルファベットのCという1文字を代入するには,以下のようにします.
1 |
code = 'c'; |
このように,通常の変数と同じように扱うことができます.
しかし,実際には変数codeにCという文字が入るのではありません.
Cという文字のASCIIコード(0x43)が代入されます.
実際には数値が入るわけですから,このCという文字のASCIIコードで’C’は整数型変数にも代入できます
1 2 |
int code2; code2 = 'C'; |
char型は1文字を扱えることがわかりましたが,文字列はどのようにして扱うのかを考えてみましょう.
文字列とは1文字1文字が連続している文字の集合なので,配列の考え方がそのまま利用できます.
すなわち,char型の配列変数(文字配列)を定義して,文字列用の変数として利用します.
この時に定義した配列の大きさが,代入できる文字列の長さになります.
例えば,以下のように変数定義します.
1 |
char string[10]; |
これは10文字まで扱える変数stringの定義ができました.
この変数には,10文字(要素)まで代入できるのですが,実際には文字列の終わりを示すコード(’\0’)が文字列の最後に入るので,代入できる文字数は9文字までになります.
文字配列を作成して表示するコード
文字配列を作成して表示するコードを以下に示します.
最初に文字列stringに1文字ずつ入力して文字列を作成し,printf関数で表示しています.
この時,文字配列の最後にNULL文字('\0')を代入することを忘れないようにして下さい.
次に,scanf関数とprintf関数で文字列の入出力を行っています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #define MAX_STR 10 int main(void) { char str[MAX_STR]; str[0] = 'a'; str[1] = 'b'; str[2] = 'c'; str[3] = '\0'; printf("str = %s\n", str); printf("Please input str: "); scanf("%s", str); printf("str = %s\n", str); return 0; } |
実行結果は以下になります.
string.cの13~16行目で1文字ずつ配列に格納した場合と,scanf関数で"abc"と入力した場合(4行目の行末)の結果が,同じになっていることがわかります(3,5行目).
1 2 3 4 5 |
$ gcc string.c $ a.out str = abc Please input str: abc str = abc |
文字配列の定義時に初期化するコード
文字配列の定義時に初期化するコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #define MAX_STR 10 int main(void) { char str[MAX_STR] = "abc"; char str2[MAX_STR] = {'a', 'b', 'c'}; char str3[MAX_STR] = {[0] = 'a', [1] = 'b', [2] = 'c'}; char str4[MAX_STR] = {[0] = 'a', [1] = 'b', [2] = 'c', [4] = 'e'}; printf("str = %s\n", str); printf("str2 = %s\n", str2); printf("str3 = %s\n", str3); printf("str4 = %s\n", str4); return 0; } |
実行結果は以下になります.
注意点として,string2.cの14行目でstr4[4]に'e'が格納されていますが,str4[3]が'\0'なので文字列としては"abc"になります.
1 2 3 4 5 6 |
$ gcc string2.c $ a.out str = abc str2 = abc str3 = abc str4 = abc |
型修飾子
型修飾子は,変数の特別な性質を表すために付けます.
const
constを付けると,読み込み専用の変数を定義できます.
変数の初期化のみが許され,その後の代入は不可能になります.
constを付けると,読み込み専用メモリ(ROM等)に置いてよい変数を明示的に指定できます.
関数の引数に利用すると,その関数内での変更が不可能になり,引数が不用意に変更されることを防ぐことができます.
constを変数に付けた場合は以下になります.
どちらも初期値を代入した後に値を変更しようとするとコンパイル時に「error: assignment of read-only location」や「error: increment of read-only variable」とメッセージが表示されてコンパイルエラーになります.
1 2 3 4 5 |
const int i = 2; // i = 3; // Compile Error const int j = 3; // j++; // Compile Error |
ポインタ変数にconstを付ける場合,アスタリスクの前後でポインタにconstを付けると以下の意味になります.
- アスタリスクの前にconst:ポインタが指す中身が読み込み専用であることを保証します.ただし,ポインタ変数の値自体は変更できることに注意して下さい.
- アスタリスクの後にconst:ポインタが指す中身を書き込み可能になりますが,ポインタ変数の値自体は読み込み専用になります.
- アスタリスクの前後にconst:ポインタが指す中身とポインタ変数の値が読み込み専用(定数)になります.
ポインタ変数にconstを付ける例を示します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { const char *str = "abc"; // *str = 'd'; // Compile Error str = "def"; // Compile OK char const *str2 = "abc"; // same as "const char *str" // *str2 = 'd'; // Compile Error str2 = "def"; // Compile OK char *const str3 = "abc"; *str3 = 'd'; // Compile OK but segmentation fault because of read-only location // str3 = "def"; // Compile Error const char *const str4 = "abc"; // *str4 = 'x'; // Compile Error // str4 = "def"; // Compile Error return 0; } |
ポインタ演算子を知りたいあなたは,ポインタとはを読みましょう.
volatile
volatileを付けると,コンパイラの最適化を抑制することができます.
例えば,a,bがint型の変数の場合,以下の演算をするとします.
1 |
*(int *) &y = x; |
しかし,コンパイラの最適化により,以下のコードに変換されてしまいます.
1 |
y = x; |
コンパイラの最適化を抑制したい場合,volatile int型で定義すれば,意味どおりにマシン語に変換されます.
このように,volatileばOSや組込み開発でCPUの制御レジスタをアドレスを利用して直接操作する場合に利用します.
他には,マルチスレッドプログラミングで,ある関数では値を変更しないけど他の関数でその値を変更する場合,その関数内では変更されない(定数である)とコンパイラが勘違いしてコードを削除してしまいます.
まずはC言語でマルチスレッドプログラミングを知りたいあなたはこちらからどうぞ.
マルチスレッドプログラミングを利用するコードは以下になります.
func_thread関数ではグローバル変数のaの値を0に設定し,aが0の間はwhileループを回ります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <unistd.h> #include <pthread.h> int a; void *func_thread(void *arg) { a = 0; printf("%s(): a = %d\n", __func__, a); while (a == 0) { } printf("%s(): a = %d\n", __func__, a); return NULL; } int main(void) { pthread_t thread; printf("%s(): a = %d\n", __func__, a); pthread_create(&thread, NULL, func_thread, NULL); sleep(1); a = 1; pthread_join(thread, NULL); printf("%s(): a = %d\n", __func__, a); return 0; } |
コンパイラの最適化オプションを付けない場合の実行結果は以下になります.正常に動作しました.
1 2 3 4 5 6 |
$ gcc volatile.c -lpthread $ a.out main(): a = 0 func_thread(): a = 0 func_thread(): a = 1 main(): a = 1 |
コンパイラの最適化オプション-O2を付けた場合の実行結果は以下になります.
この場合,func_thread関数ではaの値は0以外に変更されない(定数である)と勘違いして,コンパイラの最適化オプションを付けた場合は無限ループになってしまいました.
1 2 3 4 5 |
$ gcc volatile.c -lpthread -O2 $ a.out main(): a = 0 func_thread(): a = 0 // not finish... |
そこで,以下のようにグローバルのint型の変数aの定義でvolatileを付けてみましょう.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <unistd.h> #include <pthread.h> volatile int a; void *func_thread(void *arg) { a = 0; printf("%s(): a = %d\n", __func__, a); while (a == 0) { } printf("%s(): a = %d\n", __func__, a); return NULL; } int main(void) { pthread_t thread; printf("%s(): a = %d\n", __func__, a); pthread_create(&thread, NULL, func_thread, NULL); sleep(1); a = 1; pthread_join(thread, NULL); printf("%s(): a = %d\n", __func__, a); return 0; } |
実行結果は以下になります.正常に動作しました.
1 2 3 4 5 6 |
$ gcc volatile2.c -lpthread -O2 $ a.out main(): a = 0 func_thread(): a = 0 func_thread(): a = 1 main(): a = 1 |
コンパイラの最適化と戦うあなたへの記事もあわせて読むことをおすすめします.
restrict
restrictは,C99から導入された型修飾子で,コンパイラに「aliasが存在しないと仮定した最適化を許す」ことを伝えます.
aliasが存在しないとは,メモリ領域が重複しない(別名がない)ことを意味します.
ここで,restrictはポインタ変数には利用できますが,通常の変数には利用できないことに注意して下さい.
restrictキーワードを理解するために,/usr/include/string.hにあるmemcpy関数とmemmove関数のプロトタイプ宣言を見てみましょう.
※__THROWや__nonnull ((1, 2))のような見なれない構文の解説はひとまず置いておきます.
ここで利用している__restrictはrestrictのことを表します.(__restrictはもしコンパイラのバージョンが古い場合は空定義になるマクロです.)
1 2 3 4 |
extern void *memcpy (void *__restrict __dest, const void *__restrict __src, size_t __n) __THROW __nonnull ((1, 2)); extern void *memmove (void *__dest, const void *__src, size_t __n) __THROW __nonnull ((1, 2)); |
memcpy関数はメモリ領域をコピーしますが,コピー元の領域とコピー先の領域が重なってはならない制約があるので,restrictを付けることができます.
これに対して,memmove関数はmemmcpy関数と同様にメモリ領域をコピーしますが,コピー元とコピー先の領域が重なっていてもよいので,restrictを付けられません.
_Atomic
_AtomicはC11規格から採用された型修飾子です.
詳細を知りたいあなたは,以下の記事を読みましょう!
まとめ
C言語のデータ型を紹介しました.
具体的には,変数定義,変数名のルールと命名規則,定数,配列,文字配列,型修飾子を解説しました.
データ型はC言語の基本的な内容ですが結構奥が深いので,何度も読み込んで理解しましょう.
C言語の複素数型を知りたいあなたはこちらからどうぞ.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!