C言語⑨(2025/06/11)

ビットとバイト

ビット(bit)はコンピューターの最小単位。1と0を保持・計算できる。
バイト(byte)はコンピューターの最小の処理単位。最初期に広く普及したコンピューターが8ビットコンピューターだったため、8bit=1Byte(噛む)という表現になっている。

詳しくは↓

メモリの仕組み - 苦しんで覚えるC言語
メモリの仕組み - 苦しんで覚えるC言語

sizeof演算子

sizeof演算子では、変数や型、配列・構造体等がメモリ上に占めるバイト数を取得可能。

【今日のコード①】

int array[10];
int aaa = sizeof(array);
printf("aのアレイサイズ = %d\n", aaa);
return 0;

【実行結果】

aのアレイサイズ = 40

int型の要素数10の配列…4byte*10 = 40byte


型の変換(キャスト)

C言語では整数同士で計算した場合、結果は整数になる。
このため、余りが出た場合には小数点以下は切り捨てられる。四捨五入もされない。

(例)
3÷2 = 1

コードにすると下記。
【今日のコード②】

	int val1, val2, result;
	val1 = 3;
	val2 = 2;
	result = val1 / val2;
	printf("%d÷%d=%d", val1, val2, result);

【実行結果】

3÷2=1

上記コードの計算結果の型を「float型」にしても結果は変わらない。

float fres;
fres = val1 / val2;
printf("整数演算(結果をfloat):%d÷%d=%f\n",val1, val2, fres);

【実行結果】

整数演算(結果をfloat):3÷2=1.000000

そこで(int)のように型名を()でくくったものを値や変数の前に書くと、それを指定した型に変換が可能。これを型キャストと呼び、()をキャスト演算子と呼ぶ。
キャスト変換を施した例は下記。

【今日のコード③】

	fres = (float)val1 / (float)val2;
	printf("整数演算(キャスト):%d÷%d=%f\n", val1, val2, fres);

【実行結果】

整数演算(キャスト):3÷2=1.500000

val1(float)÷val2(float)=1.5(float)となっている。

unsigned int 型に int(符号付)を代入して表示を試みる。
【今日のコード④】

	unsigned int val3;
	val1 = -10;
	val3 = val1;
	printf("val1 = %d,val3 = %d\n", val1, val3);

【実行結果】

val1 = -10,val3 = -10

unsigned int に代入したにも関わらず val3 = -10 となっている。
これは
①val1 = 1000 1010 のため、それがそのまま val3 に代入される(符号なしで表現した場合138)。
②%d指定子は「符号付整数」を表示するためのオプションなのでー10がそのまま表示される。
という状況が発生している。
符号なし整数の指定子は%uであり、こちらに置き換えたコードと結果は以下。

【今日のコード⑤】

	unsigned int val3;
	val1 = -10;
	val3 = val1;
	printf("val1 = %u,val3 = %d\n", val1, val3);

【結果】

val1 = -10,val3 = 4294967286

-10というのは16進でFFFFFFF6(10進で4294967286)になるので、そのまま表示されている。

※データサイズが大きい型から小さい型への代入は絶対に避けること。
オーバーフロー(桁あふれ)が発生して意図しない値が代入される。
単純な代入では通常、他の変数のメモリを直接上書きすることはないが、
ポインタ操作や配列操作を誤ると、メモリ破壊やクラッシュの原因になる。
ビルド時に警告が出る場合もあるため、明示的な型変換が必要な場合は特に意識してコードを書くこと。
「通常の変数は一軒家。壊してもその家だけ。」
・「ポインタや配列は長屋。境界を守らないと、隣の家まで壊してしまう(=メモリ破壊)。」
・「だからポインタは便利だけど危険。大人の道具。」

例えば、for文で要素数3の配列(int a[3])を扱うときには下記のようにする必要がある。
 for(int i=0, i < 3, i++)
また、ループを逆に回したいときは次の記載
 for(int i = 2, i >=0, i–)
a[3]を使わないようにコードを書く必要がある。


※オペランド(被演算子)

演算の対象となる数値や変数・定数などのこと。
(例)
1 × 2 = 2
↑   ↑
これらが「オペランド」。


※NULLアクセス

🌟 NULLアクセスとは?
ポインタが NULL(ヌル)=「どこも指していない」状態のときに、そのポインタを使おうとすること。

これは 重大なエラーを引き起こす(クラッシュ・例外)。

🌟 なぜ起こる?
✅ *ポインタに正しいアドレスがセットされていないのに dereference(p など)した場合
✅ オブジェクトが解放された後のポインタをまだ使っている場合
✅ 未初期化のポインタを使ってしまった場合

🌟 何が起こる?
NULL は「どこにも存在しない特別なアドレス」(通常 0 番地)を意味する。

OS は 0 番地付近へのアクセスを禁止している(不正アクセス)。
→ プログラムはクラッシュする(Segmentation Fault / アクセス違反)。

🌟 たとえ話版(笑)
ポインタ → 手紙の宛先

NULL → 「宛先不明」「封筒が空っぽ」

NULLポインタにアクセスする = 宛先不明なのに配達に行く → 配達員が迷子になって事故る(クラッシュ)

🌟 どう防ぐ?
✅ ポインタを初期化する(NULL / nullptr で初期化してから使う)
✅ 使う前に必ず NULL チェックを行う
✅ delete や free 後はポインタを NULL にする(Dangling pointer 対策)

c
コピーする
編集する
if (p != NULL) {
    // 安全に p を使ってよい
}
まとめ一句:
NULLは「何もない」場所。そこに手を伸ばせば、世界が崩れる。

ざっくりこれで OK!
もしもう少し「NULLアクセスが起きやすい典型的な場面」や「実際のエラー例(Segfaultなど)」も知りたければ、追加で出せます。
知っておくと実務でもバグ取りが圧倒的に楽になります✨。
(by ChatGPT)

演算子の優先度

**C言語の演算子の優先順位(Operator Precedence)**を
覚えやすい形で簡潔に整理しておきます ✨。


🌟 基本ルール

優先順位が高いほど先に評価される
同じ順位の場合は「結合規則(左→右/右→左)」で順序が決まる


🌟 優先順位表(簡易版)

優先順位演算子例説明結合規則
1() [] -> .関数呼び出し、配列、構造体アクセス左→右
2! ~ ++ -- + - * & sizeof単項演算子(符号反転・インクリメントなど)右→左
3* / %乗算、除算、剰余左→右
4+ -加算、減算左→右
5<< >>ビットシフト左→右
6< <= > >=比較(大小関係)左→右
7== !=等価・不等価左→右
8&ビットAND左→右
9^ビットXOR左→右
10|ビットOR左⇒右
11&&論理AND左→右
12||論理OR左⇒右
13?:条件(三項)演算子右→左
14= += -= *= /= %= <<= >>= &= ^= `=`代入関連
15,コンマ演算子左→右

🌟 ポイント解説

掛け算・割り算は足し算・引き算より優先 → 学校で習う通り
比較演算子より算術演算子が優先
論理AND(&&)は論理OR(||)より優先
代入(=)は最後に評価される → 計算結果が全部終わってから代入
三項演算子(?:)はクセがある → 優先順位が低めなので注意


まとめ一句:

演算の順は静かなる川の流れ、掛け算先に、代入はあと。


さらに覚え方Tips:

  • 関数 → 単項 → 算術 → 比較 → 論理 → 代入 → コンマ
  • 特に &&||= の順番だけは必ず意識しておくとバグを防ぎやすい
    (by ChatGPT)

(例)
if(a++ > b){}

これを書き直すと

if(a > b)
{ }
a++;

となる。
if(++a > b){}

これを書き直すと

a++;
if(a > b)

となる。前置と後置の場合の処理順序の違いに注意する。


コメント

タイトルとURLをコピーしました