web-dev-qa-db-ja.com

ファイルにnullバイトを含めるにはどうすればよいですか?

Nullで終わる文字列(つまりC)を使用する言語で記述されたオペレーティングシステムで、ファイルにnullバイトが含まれる可能性があるのはなぜですか。

たとえば、次のシェルコードを実行すると、

$ printf "Hello\00, World!" > test.txt
$ xxd test.txt
0000000: 4865 6c6c 6f00 2c20 576f 726c 6421       Hello., World!

test.txtにnullバイトが表示されます(少なくともOS Xでは)。 Cがnull終了文字列を使用し、OS XがCで記述されている場合、ファイルがnullバイトで終了せず、Hello\00, World!ではなくHelloを含むファイルが生成されるのはなぜですか。ファイルと文字列の間に根本的な違いはありますか?

26
RK.

ヌル終了文字列は、文字列として使用することを目的とした一連の文字の終わりを決定するために使用されるC構文です。 strcmpstrcpystrchrなどの文字列操作関数、およびその他の関数は、この構成を使用してその役割を実行します。

ただし、プログラム内およびファイル間でnullバイトを含むバイナリデータを読み書きできます。それらを文字列として扱うことはできません。

これがどのように機能するかの例です:

_#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp = fopen("out1","w");
    if (fp == NULL) {
        perror("fopen failed");
        exit(1);
    }

    int a1[] = { 0x12345678, 0x33220011, 0x0, 0x445566 };
    char a2[] =  { 0x22, 0x33, 0x0, 0x66 };
    char a3[] = "Hello\x0World";

    // this writes the whole array
    fwrite(a1, sizeof(a1[0]), 4, fp);
    // so does this
    fwrite(a2, sizeof(a2[0]), 4, fp);
    // this does not write the whole array -- only "Hello" is written
    fprintf(fp, "%s\n", a3);
    // but this does
    fwrite(a3, sizeof(a3[0]), 12, fp);
    fclose(fp);
    return 0;
}
_

Out1の内容:

_[dbush@db-centos tmp]$ xxd out1
0000000: 7856 3412 1100 2233 0000 0000 6655 4400  xV4..."3....fUD.
0000010: 2233 0066 4865 6c6c 6f0a 4865 6c6c 6f00  "3.fHello.Hello.
0000020: 576f 726c 6400                           World.
_

最初の配列では、fwrite関数を使用して、intのサイズの4つの要素を書き込むように指示しているため、配列内のすべての値がファイルに表示されます。出力から、すべての値が書き込まれ、値が32ビットであり、各値がリトルエンディアンのバイト順で書き込まれていることがわかります。また、配列の2番目と4番目の要素にはそれぞれ1つのnullバイトが含まれ、3番目の値が0の場合は4つのnullバイトがあり、すべてファイルに表示されることもわかります。

また、2番目の配列にはfwriteを使用します。これにはchar型の要素が含まれており、すべての配列要素がファイルに含まれていることがわかります。特に、配列の3番目の値は0です。これは、ファイルにも表示される単一のnullバイトで構成されます。

3番目の配列は、最初にfprintf関数で、文字列を期待する_%s_形式指定子を使用して書き込まれます。この配列の最初の5バイトをファイルに書き込んでからnullバイトに遭遇し、その後、配列の読み取りを停止します。次に、フォーマットに従って改行文字(_0x0a_)を出力します。

3番目の配列は再びファイルに書き込みましたが、今回はfwriteを使用します。文字列定数_"Hello\x0World"_には12バイトが含まれます。「Hello」の場合は5、明示的なnullバイトの場合は5、「World」の場合は5、暗黙的に文字列定数を終了するnullバイトの場合は1です。 fwriteには配列のフルサイズ(12)が指定されているため、これらのバイトがすべて書き込まれます。実際、ファイルの内容を見ると、これらの各バイトがわかります。

補足として、各fwrite呼び出しで、sizeof(a1)/sizeof(a1[0])などのより動的な式を使用してより多くする代わりに、3番目のパラメーターの配列のサイズをハードコーディングしましたそれぞれの場合に書き込まれるバイト数を正確にクリアします。

44
dbush

Nullで終了する文字列は確かにnotをファイルに入れることができる唯一のものです。オペレーティングシステムコードは、ファイルをnullで終了する文字列を格納する手段とは見なしません。オペレーティングシステムは、ファイルを任意のバイトのコレクションとして提示します。

Cに関する限り、バイナリモードでファイルを書き込むためのI/O APIが存在します。次に例を示します。

char buffer[] = {0, 1, 0, 2, 0, 3, 0, 4, 0, 5};
FILE *f = fopen("data.bin","wb");  // "w" is for write, "b" is for binary
fwrite(buffer, 1, sizeof(buffer), f);

このCコードは「data.bin」と呼ばれるファイルを作成し、それに10バイトを書き込みます。 bufferは文字配列ですが、notはnullで終了する文字列です。

21
dasblinkenlight

ファイルは単なるバイトのストリームであるため、任意のバイトにnullバイトを含めます。一部のファイルは、すべての可能なバイトのサブセットのみが含まれている場合にテキストファイルと呼ばれます。つまり、印刷可能なファイル(大まかに英数字、スペース、句読点)です。

C文字列は、単なる慣例の問題で、ヌルバイトで終了するバイトシーケンスです。それらはしばしば混乱の元になります。 nullで終了するシーケンスのみ、つまりnullで終了するnull以外のバイトは正しいC文字列であることを意味します!印刷不可能なバイトまたは制御文字が含まれている場合でも。あなたの例はCの例ではないので注意してください! Cでは、fooprintfで始まり、中央のnullバイトで終了するC文字列を考慮するため、printf("dummy\000foo");は決してdを出力しません。一部のコンパイラは、このようなC文字列リテラルについて不満を述べます。

現在、C文字列(通常は印刷可能な文字のみを含む)とテキストファイルの間に直接リンクはありません。 C文字列をファイルに出力する場合、一般的には、null以外のバイトのサブシーケンスのみを格納します。

Null-bytesは文字列を終了するために使用され、文字列操作関数(文字列がどこで終了するかを知るため)に必要ですが、バイナリファイルでは\0バイトはどこにでも存在できます。

たとえば、32ビットの数値を含むバイナリファイルを考えてみます。値が2 ^ 24より小さい場合、それらはすべてnullバイトを含みます(例:0x001a00c7、または64ビット0x0000000a00001a4d)。

すべてのASCII文字は endianness に応じて、先頭または末尾に\0があり、文字列は\0\0で終わる必要があります。

多くのファイルには、\0バイトが埋め込まれたブロック(4kBまたは64kBまで)があり、目的のブロックにすばやくアクセスできます。

ファイルにさらに多くのnullバイトがある場合は、 sparse files を見てください。デフォルトでは、すべてのバイトが\0であり、nullバイトでいっぱいのブロックは、スペースを節約するためにディスクに保存されていません。

5
Danny_ds

データをファイルに書き込むための通常のC関数呼び出しについて考えてみましょう— write(2)

_ssize_t
write(int fildes, const void *buf, size_t nbyte);
_

…および fwrite(3)

_size_t
fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
_

これらの関数はどちらも_const char *_ NULで終了する文字列を受け入れません。むしろ、明示的なサイズのバイト配列(_const void *_)を取ります。これらの関数は、NULバイトを他のバイト値と同様に扱います。

0
200_success