
C言語基礎|バイト順序とエンディアン
同じ数なのにメモリの並びが違う!?バイト順序(エンディアン)を知ると、ポインタとデータの見え方がつながる。
C言語でポインタを学び始めると、アドレスを表示したり、メモリの中身をバイト単位で覗いたりする機会が増えます。
そのときに必ず出会うのが バイト順序、いわゆる エンディアン(endianness) です。
同じ 0x12345678 という数値でも、メモリ上に 12 34 56 78 の順で並ぶとは限りません。
環境によっては 78 56 34 12 の順に入ります。
これを知らないと、ポインタでバイト配列として読み取ったときに
「えっ、値が逆に見える!」って混乱しやすいんです。
ここを先に押さえておくと、ポインタ・配列・構造体・通信・ファイル保存が一気に理解しやすくなりますよ。
バイト順序とは(複数バイトの値がどう並ぶか)
char は 1バイトですが、int や long、double などは 複数バイトで表現されます。
その複数バイトをメモリ(低いアドレス→高いアドレス)に並べる順番が バイト順序 です。
1バイトと複数バイトの違い
| 型の例 | 典型的な大きさ | メモリ上の見え方 |
|---|---|---|
| char | 1バイト | 並び順の問題が起きにくい |
| int | 4バイト(多くの環境) | 4つのバイトの並びが問題になる |
| double | 8バイト(多くの環境) | 8つのバイトの並びが問題になる |
※正確なサイズは処理系で変わるので、sizeof で確認できます。
エンディアン(little / big)を図でつかむ
ここでは説明を分かりやすくするため、32ビット値(4バイト)を例にします。
値は 0x12345678 とします。
図:同じ値でも並びが2種類ある
リトルエンディアン(little endian)
下位バイト(78)が低アドレス側に来ます。

ビッグエンディアン(big endian)
上位バイト(12)が低アドレス側に来ます。

この図の読み方(大事)
- 「低アドレス→高アドレス」は、メモリを左から右に並べたイメージです。
- 同じ 0x12345678 でも、先頭(最小アドレス)のバイトが変わります。
- 先頭アドレスを指すポインタで 1バイトを読むと、見える値が変わることがあります。
なぜエンディアンが重要?(ポインタでバイトを見ると差が出る)
ポインタは「オブジェクトの先頭アドレス」を指します。
つまり、unsigned char * のように バイト単位で覗くと、先頭バイトが何になるかはエンディアンに依存します。
先頭バイトがどう見えるか
| 32ビット値 | リトルエンディアンで先頭バイト | ビッグエンディアンで先頭バイト |
|---|---|---|
| 0x12345678 | 0x78 | 0x12 |
この違いが、バイナリ解析・通信・ファイル形式の読み書きで効いてきます。
ポインタの基本(& と * をやさしく整理)
ここからは、エンディアンの話とつながる「ポインタで覗く」ための基本です。
アドレス演算子 &
- 役割:オブジェクトのアドレス(場所)を取り出す
- 例:&n は n の先頭アドレス
間接演算子 *
- 役割:ポインタが指す先の値にアクセスする
- 例:p が n を指すなら、*p は n と同じものとして扱える(読み書きできる)
& と * の対応
| 記号 | 呼び方 | 意味 | 例 |
|---|---|---|---|
| & | アドレス演算子(単項) | 住所を取り出す | &n |
| * | 間接演算子(単項) | 指している先を読む/書く | *p |
※ビットANDの &(2項)と混同しないように、1つに付く & はアドレス演算子と覚えるのがコツです。
サンプルプログラム
目的はシンプルです。
1つの 32ビット値を用意して、ポインタで先頭から4バイトを表示し、エンディアンの違いが見えるようにします。
プロジェクト名:chap10-3-1 ソースファイル名:chap10-3-1.c
#include <stdio.h>
int main(void)
{
unsigned int v = 0x12345678;
puts("4バイトの並びを先頭から確認します。");
unsigned char *p = (unsigned char *)&v;
printf("値 v : 0x%08X\n", v);
printf("v のアドレス : %p\n", (void *)&v);
printf("先頭バイト p[0]: 0x%02X\n", p[0]);
printf("次のバイト p[1]: 0x%02X\n", p[1]);
printf("次のバイト p[2]: 0x%02X\n", p[2]);
printf("次のバイト p[3]: 0x%02X\n", p[3]);
return 0;
}実行結果のイメージ(例)
- リトルエンディアンなら、先頭から 78 56 34 12 のように出やすい
- ビッグエンディアンなら、先頭から 12 34 56 78 のように出やすい
※実際の結果は環境で変わります(ここがまさにエンディアンの話です)。
このサンプルで何をしているか
v をバイト列として覗いているイメージ
v(unsigned int): 0x12345678
p(unsigned char*)は v の先頭アドレスを指す
p[0], p[1], p[2], p[3] で 1バイトずつ読む
ここでのポイント(重要)
- v は 4バイトの塊(多くの環境)としてメモリに置かれる。
- (unsigned char *)&v によって「1バイト単位の視点」に切り替えている。
- p[0] は「先頭アドレスの1バイト」なので、エンディアンで中身が変わる。
登場する命令(関数)と書式・何をする?
このサンプルで使った「命令」は主に puts と printf です。加えて、キャストや配列アクセスも重要なので合わせて整理します。
puts
- 書式:puts(文字列);
- 何をする?:文字列を表示して改行も出します。
- 今回の役割:説明文を1行出す。
printf
- 書式:printf(書式文字列, 引数1, 引数2, ...);
- 何をする?:書式に従って値を表示します。
- 今回の役割:16進表示やアドレス表示、バイト表示
(void *) へのキャスト(アドレス表示の作法)
- 例:(void *)&v
- 何をする?:ポインタの型を void * に合わせる(%p に渡すときの作法)
- ねらい:環境差による警告を避け、読みやすくする。
(unsigned char *) へのキャスト(バイト単位で覗く)
- 例:unsigned char *p = (unsigned char *)&v;
- 何をする?:v の先頭アドレスを「1バイト単位で扱うポインタ」に変換する。
- ねらい:p[0] などで、メモリの生バイトを確認する。
printf の変換指定の意味(今回よく使う)
| 変換指定 | 表示内容 | 例 |
|---|---|---|
| %08X | 8桁・0埋めの16進(大文字) | 0x12345678 をそろえて表示 |
| %02X | 2桁・0埋めの16進(バイト向け) | 0x7A など |
| %p | ポインタ(アドレス) | (void *)&v |
ちょい注意:エンディアンはどこで困る?
エンディアンは「普段のCの計算」では意識しなくても動くことが多いです。困るのは主にこういう場面です。
表:困りやすい場面
| 場面 | 何が起きる? | 対策の考え方 |
|---|---|---|
| ネットワーク通信 | バイト順が規定されていることが多い | 送受信時に変換する(プロトコル仕様に従う) |
| バイナリファイル | 別環境で読むと値が逆に見えることがある | ファイル形式のエンディアンに合わせる |
| 生メモリ解析 | 先頭バイトの意味が変わる | エンディアンを前提に読む |
使った表や図の説明(読み方のコツ)
- エンディアンの図は「低アドレス→高アドレス」の方向に、1バイト箱を並べています
先頭箱が p[0] だと思って見ると、ポインタと直結して理解できます。 - サンプル分解図は「型の視点」を切り替えている点がポイントです。
unsigned int は 4バイトの塊、unsigned char * は 1バイト刻みで覗く視点、という感じです。 - 変換指定の表は、表示が崩れやすい箇所(%p、%02X、%08X)をまとめています。
実験コードの見た目が整うと、理解も一気に進みます。
