Linux

【初心者向け】C言語の基本的な文法(前編)

【※ 当記事は2020年7月23日時点の情報です】

ペイヴメント(@pavement1234)です。

エンジニア
エンジニア

C言語の基本的な文法をざっくり網羅的に知りたい。

バージョン情報
Windows10 Home(64bit)1903
VMware Player 15上で動くUbuntu18.04

前編で出来ること

C言語の基本的な文法をざっくり紹介します。
極力さらっと読める感じにしていますが、
たまにマニアックな話題(中・上級者向けのネタ)に触れています。

前編ではカンタンなプログラム実行方法(HelloWorld)
①プリプロセッサ指令、
②変数、
③定数(リテラル)、
④分岐処理、
⑤反復(ループ)処理、
⑥演算子
を説明します。

C言語開発環境をつくる方法

①Linuxで開発する

≫【Ubuntu18.04】プログラミング環境を無料で構築する方法

②Windowsで開発する

≫【MinGW】無料のC/C++プログラミング環境をインストールしよう

今回のサンプルはLinux、Windows
どちらでも実行できるように記載しています。

以降、コンパイル&プログラム実行はLinuxを前提に記載していますが、
Windowsで実行する場合、gcc構文は同じでOK
プログラム実行時は「./test」を「test.exe」に読み替えてください。

超カンタンなプログラムの書き方(HelloWorld)

テキストエディタを開き「test.c」というファイルを作成。
以下の内容をコピペして保存。

サンプルプログラム:Hello World

コンパイル&プログラム実行。
HelloWorldと表示されます。

$ gcc -g -o test test.c

$ ./test
Hello World

解説します。
よく分からない用語が出てきたら是非自分で調べてみてください。

1~3行目:コメント
/**/で囲うことで複数行に渡るコメントを書けます。
//で行コメントにすることもできます。

≫参考: C言語入門:コメントの書き方(Geekなページ)

4行目:インクルード宣言。
標準入出力に関する関数を定義したstdio.hというヘッダーファイルを読み込みます。
stdio.hを読み込むことで7行目のprintf関数を呼ぶことができます。
stdioはSTandarDInputOutputの略語です。

≫参考:#include <stdio.h>はおまじないじゃないぞ

5行目:メイン関数。
C言語のプログラムはmain関数から実行開始するルールになっています。
argc(argument counter)に引数の個数、
argv(argument vector)に引数の配列が格納されます。

※main関数を実行する前にアセンブラのプログラムが実行され
スタックメモリの初期化などC言語のプログラムを実行するための準備をしますが
今回は説明しません。

≫参考:argc,argvとは?(Qiita)

6行目:ブロック開始。

≫参考:ブロック_(プログラミング)(Wikipedia)

7行目:printf関数
HelloWorld\n”という文字列を標準出力(stdout)に表示します。
\nは改行コードです。

≫参考:C言語関数辞典:printf

8行目:リターン文。
プログラムの終了コード(今回は0)を返却します。

≫参考:C言語入門:関数(2):return文(Geekなページ)

9行目:ブロック終了。

ちょっと面倒に思われるかもしれませんが
慣れないうちは上記のように
「インクルード宣言とは?」
「main関数とは?」
「printfとは?」
「returnとは?」と
1行ずつ意味を調べて
コツコツとC言語の語彙を増やしていくことをオススメします。

ちょっと調べたいときはインターネットで検索できますし、
体系的に学びたいときはC言語の本を読めばOKです。

①プリプロセッサ指令、
②変数、
③定数(リテラル)、
④分岐処理、
⑤反復(ループ)処理、
⑥演算子

①プリプロセッサ指令(ディレクティブ)

プリプロセッサ指令(ディレクティブ)は
コンパイルの前処理を指示します。

構文 意味

#include <標準ヘッダファイル.h>
例:#include <stdio.h>

#include “自作ヘッダファイル.h”
例:#include “my_function.h”

ファイルの読み込み。

#define 定数名    定数
例:#define AUTHER_NAME    ( “pavement1234” )
例:#define MAX_VALUE    ( 12345 )

#define マクロ名( 引数, … )    処理
例:#define ADD( a, b )    ( a + b )

定義済マクロ
__FILE__ ファイル名
__LINE__ 行番号
__DATE__ コンパイル日付
__TIME__ コンパイル時間
__STDC__ ANSI規格対応なら1、
非対応なら0

定数、マクロ定義。

#error ”エラーメッセージ”
例:#error “Prosessor name is unknown.

コンパイル時にエラーを発生させます。

条件コンパイル(#ifディレクティブなど)
と組合わせて利用するのが普通です。
例えばプロセッサ名が未知のためヘッダファイルが特定できないなど、
コンパイル継続が難しいときにエラーを発生させたりします。

#warning “警告メッセージ”
例:#warning “Function AAA is not recommended”

 

コンパイル時にワーニングを発生させます。

条件コンパイル(#ifディレクティブなど)と組合わせて利用するのが普通です。
例えば推奨されない関数を利用する場合など、コンパイル時に警告メッセージを出したりします。

#if    条件1
    条件1のときにコンパイルする処理。
#elif    条件2
    条件2のときにコンパイルする処理。
#else
 条件1、2に該当しない場合に
 コンパイルする処理。
#endif

条件コンパイル

#if defined(マクロ名)で#ifdefと同じ効果が得られます。

#if !defined(マクロ名)で#ifndefと同じ効果が得られます。

#ifdef ( #ifndef ) 定数またはマクロ
 マクロに一致(不一致)したときに
 コンパイルする処理。
#else
 マクロに一致しない(一致する)
 ときにコンパイルする処理。
#endif

条件コンパイル。

#pragma  プロセッサやOS依存の命令

例:#pragma once //同じヘッダファイルを読み込ませない。

例:#pragma comment( lib, “.lib”) //リンクするライブラリを指定。

プロセッサやOS依存の命令。
※VC++で多用されています。

 

#
例:#define TO_STRING( str )    #str

TO_STRING( abcde )
//マクロ展開結果は
 ”abcde”という文字列になる。
 変数名などを文字列に変換できる。

マクロ実引数を文字列化する。

##
例:#define MERGE_STRING( a, b )    a ## b

MERGE_STRING( “param”, “1” )
// マクロ展開結果は
 ”param1″という文字列になる。

前後の字句列を結合する。

【ノウハウ】2重インクルード防止

たとえば、my_func.hという自作ヘッダファイルを作ったときに、
複数のCソースからmy_func.hをインクルードされても
2回以上読み込ませないようにするための仕組みです。

my_func.hはmy_func.c、AAA.c、BBB.c
3つのCソースからインクルードされていますが、
1回目で__MY_FUNC__というマクロが定義され
2回目以降は#ifndef __MY_FUNC_H__の条件に合致しないため
ヘッダファイルの内容は読み込まれません。

サンプルプログラム:2重インクルード防止

my_func.hがmy_func.c、AAA.c、BBB.cでインクルードされるが
2重インクルード防止しているので1回だけ読み込まれます。
2重インクルード防止しないと同じファイルが複数回読み込まれてしまいます
例えば、2重インクルード防止していないヘッダファイルに変数を定義すると、
インクルードされた回数分、
変数が生成され多重定義エラーになります。

my_func.h

my_func.c

AAA.c

BBB.c

【ノウハウ】アライメント調整

構造体(中編に出てきます)のアライメントを調整するときに
#pragmaを使うことがあります。

サンプルプログラム:#pragmaによるアライメント調整

②変数

変数とは値を保存するメモリ領域です
保存する値によって型が変わります。

以下の例ではintという整数型のaという変数を定義し、
aに12345という値を代入します。

サンプルプログラム:変数宣言と代入

32bitプロセッサの例です。

型名 意味 値の範囲

char

符号あり文字型(8bit) -128~+127

unsigned char

符号なし文字型(8bit) 0~+255

short

符号あり短整数型(16bit) -32768~+32767

unsigned short

符号あり短整数型(16bit) 0~+65535

int

符号あり整数型(32bit)

処理系依存

32bitプロセッサの場合longと同じと考えてよい

unsigned int

符号あり整数型(32bit)

処理系依存

32bitプロセッサの場合unsigned longと同じと考えてよい

long

符号あり長整数型(32bit) -2147483648~+2147483647

unsigned long

符号あり長整数型(32bit) 0~+4294967295
float 短精度浮動小数点型(32bit)

処理系依存

double 倍精度浮動小数点型(64bit)

処理系依存

【ノウハウ】符号あり、なしとは何か?

符号ありとはプラスとマイナスの値を表現できることを意味します。
最上位ビット(黄色セル)が0なら正の数、1なら負の数を表します。


一方、符号なしとは8ビット全てを使って値を表現します。

ビットパターン 符号あり(2の補数) 符号なし
00000000 0 0
00000001 1 1
01111110 126 126
01111111 127 127
10000000 -128 128
10000001 -127 129
10000010 -126 130
11111110 -2 254
11111111 -1 255

≫参考:符号付数値表現(Wikipedia)

【ノウハウ】
変数にはスコープ記憶クラスという概念があり、
参照できる範囲と保存する領域が定められています。

サンプルプログラム:変数スコープと記憶クラス

変数 スコープ 記憶クラス
a グローバル(プログラム全体) スタティック領域(永続的)
b グローバル(ファイル全体) スタティック領域(永続的)
c ローカル(main関数内) スタック領域(局所的)
d ローカル(main関数内) スタティック領域(永続的)
e ローカル(main関数内) ヒープ領域(mallocで獲得しfreeで返却する)
f ローカル(if文内) スタック領域(局所的)

【ノウハウ】型修飾子について

変数の型の前に型修飾子を付けることで型を修飾できます。

型修飾子 意味
signed, unsigned

データの符号の有・無を指定できます。
sigined → 符号あり
unsigned → 符号なし

short, long

intを修飾します
short int aaa;
 → short aaa;に省略できる。
long int bbb;
 → long bbb;に省略できる。

auto

オート変数。
ローカル変数のことです。
関数呼び出し時に確保された
スタック領域に割り当てられます。

C言語は関数内で変数を定義するとローカル変数になるので指定不要
(使わなくてよい)。
B言語との互換性のために残された
修飾子のようです。

register

レジスタ変数を定義します。
CPUによって仕様が異なりますが、
高速アクセスが可能なSRAM領域にマッピングされた汎用レジスタが用意されていることが多く
register指定した変数は汎用レジスタに割り付けようとします。
が、汎用レジスタに空きがないと普通のDRAMにマッピングされます。

extern

externを付けることでグローバル変数を他ファイルから参照できます。
externが無いと他ファイルからグローバル変数を参照できず
コンパイル時に未定義エラーになります。

const

定数を作るときに指定します。
const int aaa = 123;
 → 以降値を変更できない。

static

staticを付けることで局所化できます。
プログラムの複雑化を防止するのに
役立ちます。

関数宣言の前につけるとファイル内でのみ参照できる関数になります。

関数外(グローバル領域)に定義されたstatic変数ファイル内でのみ使えるグローバル変数になります。

関数内で定義されたstatic変数関数内でのみ参照できるグローバル変数になります。これ意外に知らない人多いです。

関数内で定義されたstatic変数は値を永続的に保持するためリエントラント(再入可能)性がなくなります

リエントラント性とは複数のスレッドなどから同時に呼ばれても同じ動作がでことを意味しますが、static変数の値が呼ばれる度に変化するので動作が毎回変わります

リエントラント性がない関数は一般的にスレッドセーフでない(スレッドで使えない)関数になります。

③定数(リテラル)

定数(リテラル)とは固定値のことです。

以下の例のように
「#defineマクロで値を定義する方法」と
「constを付けた変数を定義して固定値を設定する方法」があります。

定数(定数)はコンパイル時に決定され書き換えできません。
#defineマクロはプリプロセス時に固定値(リテラル)が確定しますし、
constを付けた変数を書き換えようとするとコンパイルエラーが出ます。

④分岐処理

if~else if~else

if~else if~elseを使うと
条件によって処理を変えることができます。

サンプルプログラム:if~else if~else

switch文

変数の値によって処理を分岐するときに
switch文を使います。

case1: case2:のように
breakを省略してcase文を連続で書くのをフォールスルーと言って、
バグの元です。

意図的にフォールスルーするのは良いのですが
単にbreak忘れでもコンパイルエラーにならないので注意しましょう。

サンプルプログラム:switch

三項演算子

条件文 ? 条件文が真のときの処理 : 条件文が偽のときの処理;
という構文になります。

以下の例はparamが0以上ならそのままaに代入。
paramが0より小さいならマイナスをつけてaに代入しています
(絶対値が得られます)。

三項演算子を使うことで行数を減らすことが出来ますが、
直観的にわかりにくいし、
デバッガでステップ実行したときに値の変化を追いにくいのであまりオススメしません。

サンプルプログラム:三項演算子

⑤反復(ループ)処理

for文

for( 初期化処理; 条件文; ループ毎の終了処理 ){ 処理 }という構文です。

最初にiという変数を0に初期化。
iが10未満ならiの値と半角スペースをprintfしてiをインクリメント(+1)。
iが10になったらループを抜けます。

実行すると0 1 2 3 4 5 6 7 8 9 と表示されます。

サンプルプログラム:for文

while文

while( 条件文 ){ 処理 }という構文です。

ループに入る前にiという変数を定義して0に初期化。

iが10未満ならiの値と半角スペースをprintfしてiをインクリメント(+1)。
iが10になったらループを抜けます。

実行すると0 1 2 3 4 5 6 7 8 9 と表示されます。

サンプルプログラム:while文

do while文

do { 処理 } while( 条件文 );という構文です。

サンプルプログラム:do while文

ループに入る前にiという変数を定義して0に初期化。
iの値と半角スペースをprintfしてiをインクリメント(+1)。
iが10になったらループを抜けます。

実行すると0 1 2 3 4 5 6 7 8 9 と表示されます。

do whileは直観的にわかりにくいため、
あまりオススメしません。

continue

ループの中の処理を実行したくないときはcontinueします。

以下の例はiが10未満ならprintfをするループですが、
iが5のときはprintfしたくない場合continueします。

実行すると0 1 2 3 4 6 7 8 9 と表示されます(5はprintfされません)。

サンプルプログラム:continue

break

ループを途中で抜けるときはbreakします。

以下の例はiが10未満ならprintfをするループですが、
グローバル変数errが0でない(エラーが発生した)場合はループを抜けます。
ループ処理中に緊急事態が発生したときによくやる手法です。

例えばiが8のときにerrがセットされた場合、
0 1 2 3 4 5 6 7 と表示されたところでbreakが呼ばれループを抜けます。

サンプルプログラム:break

⑥演算子

演算子には優先順位と結合規則があります。
同じ優先順位の場合、結合規則の方向(←なら右から左)に計算がされます。

優先順位 結合規則 種別 構文 意味
1 左→ その他 a() 関数呼び出し
1 左→ その他 a[x] 配列添え字
1 左→ その他 a.x 構造体実体のメンバ参照
1 左→ その他 a->x 構造体ポインタのメンバ参照
1 左→ 算術演算子 a++ +1
1 左→ 算術演算子 a -1
2 ←右 算術演算子 ++a +1
2 ←右 算術演算子 a -1
2 ←右 算術演算子 +a
2 ←右 算術演算子 a
2 ←右 論理演算子 !a 否定
2 ←右 ビット演算子 ~a ビット反転
2 ←右 その他 *a ポインタ
2 ←右 その他 &a アドレス
2 ←右 その他 sizeof( a ) メモリサイズ参照
3 ←右 型変換演算子 (a)x キャスト
4 左→ 算術演算子 a * x
4 左→ 算術演算子 a / x
4 左→ 算術演算子 a % x
5 左→ 算術演算子 a + x
5 左→ 算術演算子 a x
6 左→ ビット演算子 a << x 左ビットシフト
6 左→ ビット演算子 a >> x 右ビットシフト
7 左→ 比較演算子 a < x aはxより小さい
7 左→ 比較演算子 a > x aはxより大きい
7 左→ 比較演算子 a <= x aはx以下
7 左→ 比較演算子 a >= x aはx以上
8 左→ 比較演算子 a == x aとxは一致
8 左→ 比較演算子 a != x aとxは不一致
9 左→ ビット演算子 a & x ビットAND
10 左→ ビット演算子 a ^ x ビットXOR
11 左→ ビット演算子 a | x ビットOR
12 左→ 論理演算子 a && x aかつx
13 左→ 論理演算子 a || x aまたはx
14 ←右 その他 a ? x : y aが真ならx偽ならy
15 ←右 その他 a = x aにxを代入
15 ←右 算術演算子 a += x aにa+xを代入
15 ←右 算術演算子 a -= x aにa-xを代入
15 ←右 算術演算子 a *= x aにa*xを代入
15 ←右 算術演算子 a /= x aにa/xを代入
15 ←右 算術演算子 a %= x aにa%xを代入
15 ←右 ビット演算子 a <<= x aにa<<xを代入
15 ←右 ビット演算子 a >>= x aにa>>xを代入
15 ←右 ビット演算子 a &= x aにa&=xを代入
15 ←右 ビット演算子 a |= x aにa|=xを代入
15 ←右 ビット演算子 a ^= x aにa^=xを代入
16 左→ その他 a, b, c 列挙

まとめ

中編に続きます。

ABOUT ME
ペイヴメント
ペイヴメントのエンジニア塾(当ブログ)では20年以上の経験から得られたプログラミング系ノウハウについてベテランにも満足して頂けるような内容の濃いコンテンツを初心者にも分かりやすい形で日々発信しています。【経歴】ベンチャーのソフトハウスで4年勤務後、精密機器メーカーのソフト開発部門に勤務し今に至ります。
error: Content is protected !!