0

リトルエンディアン環境で、C言語で文字列をファイルに書き込み、そのファイルから再度読み込むプロセスについてお聞きします。

コード例:

char ch[]="AB”;

実行中のchの先頭アドレスにはAが格納されていることは理解しました。

ABをファイルに出力すると、当然ファイルの内容はABというテキストとなると思います。

今度は、ファイルからABをメモリに読みこもうとすると、リトルエンディアンなのでメモリ的にはBAという順番になるかと思いますが、実際はファイルの内容がそのまま(ABとして)ストアされていました。

readシステムコールがバイトオーダーを考慮してスワップ処理しているのか?
1byteずつAとBを読み込んでいるのか?

どういう仕組みでバイトオーダーが処理されているのか理解できません。

CC BY-SA 4.0

3 件の回答 3

2

まず、バイトオーダーはファイルに保存する際に発生するものではなく、メモリ内に保存されている時点ですでに発生しているものです。

また、バイトオーダーにより、配列内の要素の順番が変わることはありません。

char ch[]="AB";

こちらの配列は、ビッグエンディアンでもリトルエンディアンでも、以下の順番で保存されます。

アドレス
0000 'A'
0001 'B'
0002 '\0'

バイトオーダーで変わるのは、配列内の各々の要素のバイト順です。
例えば、shortが2バイトだったとして、

short s[] = {0x1234, 0x5678};

こちらの配列は、ビッグエンディアンだと以下の順番で保存されます。

アドレス
0000 0x12
0001 0x34
0002 0x56
0003 0x78

リトルエンディアンだと以下の順番で保存されます。

アドレス
0000 0x34
0001 0x12
0002 0x78
0003 0x56

ビッグエンディアンでもリトルエンディアンでも、配列の先頭の要素がアドレス0000~0001に、2番目の要素がアドレス0002~0003に保存されているというのは変わらないと分かると思います。

CC BY-SA 4.0
1
  • やはりどこの誰かも分からないやつの論点ズレズレの解説を聞くくらいなら自分で調べるしかなさそう、という結論に至りました。 48分前
1

ファイルからABをメモリに読みこもうとすると、リトルエンディアンなのでメモリ的にはBAという順番になるかと思いますが

いいえ、ファイルI/Oはバイトオーダーの影響を受けません。書き込んだ順序通りに読み込めます。

CC BY-SA 4.0
1

「バイトオーダー」というのは2バイト以上のデータをバイト単位で表現する場合にどのような順番にするかという話です。char型のような1バイト単位で扱うデータについてはバイトオーダーは関係ありません。


以下、蛇足です。

質問のコードからC言語のことでの話を知りたいようなので、以下、C言語を前提で話をします。また、現在のほとんどの実装にあわせて、1バイトは8ビットとして話をします。

この「2バイト以上のデータ」というのはC言語におけるshort型やwchar_t型のように2バイト以上の整数型のことです(float等の浮動小数点数型はそもそも実装依存であり、バイトオーダーという考え型自体がない)。char型は1バイトの整数型であり、その配列であるchar[]型もバイトオーダーは関係ありません。

バイトオーダーが関係するのは2バイト以上のデータ(数値)をメモリ上やバイトストリーム上で表現したときにどうなるかです。メモリ上のデータのバイトオーダーは環境依存であり、C言語であれば、共用体を使うことで確認できます。

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

union uint {
    uint8_t u8[8];
    uint16_t u16[4];
    uint32_t u32[2];
    uint64_t u64[1];
};

int main(void)
{
    union uint i;
    i.u64[0] = UINT64_C(81985529216486895);
    printf("uint64_t: %016" PRIX64 "\n", i.u64[0]);
    printf("uint32_t: %08" PRIX32 " %08" PRIX32 "\n", i.u32[0], i.u32[1]);
    printf("uint16_t: %04" PRIX16 " %04" PRIX16 " %04" PRIX16 " %04" PRIX16
           "\n",
           i.u16[0], i.u16[1], i.u16[2], i.u16[3]);
    printf("uint8_t: %02" PRIX8 " %02" PRIX8 " %02" PRIX8 " %02" PRIX8
           " %02" PRIX8 " %02" PRIX8 " %02" PRIX8 " %02" PRIX8 "\n",
           i.u8[0], i.u8[1], i.u8[2], i.u8[3], i.u8[4], i.u8[5], i.u8[6],
           i.u8[7]);
    return 0;
}

最初のuint64_tの16進数での数値と比べて、uint8_tにしたとき、小さい順(リトルエンディアン)か大きい順(ビッグエンディアン)かを確認できるでしょう。例えば、x86_64のCPUを用いている環境ではリトルエンディアンであるため、一番最後は"EF CD AB 89 67 45 23 01"と表示されるでしょう。

バイトストリーム上の表現というのは、他のプログラムなどとデータをやり取り場合にどのような順番でバイトを渡すのかという話です。例えばネットワークの多くのプロトコルではビッグエンディアンを使用しています(そのため、ビッグエンディアンはネットワークバイトオーダーとも言われます)。IPアドレスは192.168.1.2のように表現しますが、実際は3232235778等の4バイト(32ビット)の数値です。ネットワークのパケットというのはバイト単位で(もっと下のデータリンク層ではビット単位)処理しますが、パケットの宛先として3232235778というIPアドレスを表現するは、0xC0(192)、0xA8(168), 0x01(1)、x02(2)という大きい順にしたデータをパケットに含めることになります。ネットワークのライブラリ等はこういった処理を環境のバイトオーダーに依存しない形で手動で行っていることになります。

上のことは2バイト以上の数値を表現するにはどのようにするのかの話です。数値を書き込む場合はprintf等を用いて数値を数字のフォーマットに変換するため、バイトオーダーは関係無くなります。CPUが異なっても、数字の表現は変わりません。

文字列をファイルに保存の話に戻りましょう。最初にの述べたとおり、char型は1バイトであるため、バイトオーダーは関係ありません。charの配列をfputs等でそのまま書き込んだ場合、その配列の順番で各バイトが書き込まれることになります。ASCIIまたはASCII互換な文字コードの環境においては、"AB"という文字列は{65, 66, 0}というcharの配列でありfprintf(file, "AB");は、fileに65('A'と言う文字)、66('B'という文字)が書き込まれることになりまうす(fprintfはnull文字で書き込みを停止するため、0は書き込まれません。)。この処理に65というchar型の数値は1バイトの表現であるため、バイトオーダーとか全く関係無く、書き込まれることになります。また、charの配列は、UT8-8やWindows-31Jのような可変幅の文字符号化形式になっている場合もあります。可変幅の文字符号化形式ではASCII互換な部分は1文字1バイトに対して、それ以外の文字を2バイト以上で表現しますが、その2バイト以上は並びがセットであり、shortのような一つの数値という訳ではありません。そのため、バイトオーダーというのものはなく、文字符号化形式での表現にそって書き込まれることになります。

では、文字列全てにおいてバイトオーダーが無関係であるかというとそうではありません。C言語にはwchar_t型というワイド文字があり、これは2バイト以上の数値です。wchar_t型の文字符号化形式は環境依存であり、多くの場合はUTF-16またはUTF-32です。UTF-16もUTF-32も、UTF-8と同じくUnicodeの符号化形式ですが、UTF-8とは違い、UTF-16とUTF-32は一つの文字(データ)が2バイトまたは4バイトになるため、バイトオーダーという概念があります。メモリ上のバイト表現も、short等の2バイト以上の数値と同じように環境によってリトルエンディアンだったりビッグエンディアンだったりします。

このワイド文字をファイルに書き込む場合、fputs等のchar型配列文や字列用の関数では無くfputws等のwchar_t型配列文字列用の関数を用いる必要があります。個々で注意すべき事は、fputwsは、fputsのように文字のバイト表現をそのままファイルに書き込むのでは無く、書き込み先のIOで設定された文字コード(fopen等で"w,css=UTF-8"等のように指定する)、または、現在のロケール(setlocale関数で変更できる)によって変わります。例えば、UTF-16LE(UTF-16のリトルエンディアン)が指定されている(fopen("ファイル名", "w,css=UTF-16LE")のように開く)場合は、wchar_tがUTF-16であろうが、UTF-32であろうが、環境がリトルエンディアンとかビッグエンディアンとか関係無く、ファイルにはリトルエンディアンのUTF-16としてのバイトの並びで文字が入力されることになります。これは、文字列がメモリ上の表現でバイトオーダーがどうなっているかは一切関係なく、ファイルを書き込み時に指定された文字符号化スキーム(先程の話のUTF-16LEのこと)によってバイトオーダーが決まることになります。

CC BY-SA 4.0

この質問に回答するには、ログインする必要があります。

求めていた回答ではありませんか? のタグが付いた他の質問を参照する。