読者です 読者をやめる 読者になる 読者になる

yumetodoの旅とプログラミングとかの記録

旅や登山の記録やプログラミング関連の話とかフリーソフト紹介とか

拡張子とファイルのフォーマット

strimuer213p.hatenablog.com

先日Twitterでどうにも りーま氏(@strimuer213p)に拡張子とファイルのフォーマットは関係ないということが伝えられなかったので、ちょっと記事にしてみる。

ファイルの種類

まずいろんなファイルを片っ端からバイナリエディタで開いてみて欲しい。 Windowsをお使いならFavBinEditLinuxをお使いならGHexなどがいいのではないか。もちろんそんなところで宗教戦争をするつもりはないので、好きなものを使えばいい。
おれはコマンドがいいというなら、odコマンドがUnixにはあるし、GNU拡張のodコマンドはasciiの表示もしてくれる。

hex dump

さて、開いてみてわかる通り、つまるところファイルとはただ単にbyte列の羅列だ。
多くのバイナリエディタは16進数でbyte列を表示してくれるが、別に8進数で表示するもののあるし、単に見た目の問題だ。
つまりすべてのファイルは(厳密には)バイナリファイルだと言える。

これをとりあえず大きく2つに分類する。

テキストファイル

コンピュータで文字を扱うときにどうするかというと、特定のbyte列で文字を表す。文字をその特定のbyte列に直すことを、文字エンコード、と言ったりする。
この変換の規則が(広義での)文字コードである。

広義での、とぼかして書くのには理由があって、実際にはかなりややこしいことになっているのだが、

equj65.net blog.shibayu36.org

それはこの辺のサイトに説明を譲る。本題ではないからだ。

話を戻す。

とにかく、ある一つの文字コードによってエンコードされたbyte列のみで構成させるファイルをテキストファイルと呼ぶ。

(一般的な意味での)バイナリファイル

先ほど、

つまりすべてのファイルは(厳密には)バイナリファイルだと言える。

と書いたが、一般にバイナリファイルと言われて指すものはそうではなく、すべてのファイルからテキストファイルを除いたものである。

テキストファイルの種類

テキストファイルといってもさまざまな種類がある。 どういうことかというと、何らかのルールにしたがって羅列されたテキストファイル、というのがある。

例えばHTMLは、W3Cが定めたHTML規格に則って文字列を記述することで、例えばブラウザでいわゆるWebページを作れる。
Markdownは一応CommonMarkが標準規格としてあって、それにしたがって記述すれば、適当なツールでHTMLに変換できる(GFMはなんなんだという話はさておき)。
xmlは特定のデータ集合をソフトウェア間でやり取りするときに用いられ、xml上でのデータ構造そのものに意味がある。

(一般的な意味での)バイナリファイルの種類

バイナリファイルの形式は千差万別である。 C言語の構造体をそのまま書きだしたものと思ってよく、したがってバイナリファイルを扱うには処理系依存との戦いが開催される(C言語規格書の処理系依存部分との戦いの他に、エンディアン(バイトオーダー)との戦いがある)。多くのバイナリファイルフォーマットはバイトオーダーについて規定している(していないフォーマットはバグっている)。 このポータビリティ(可搬性)の低さと、多くのプログラミング言語で扱いが難しいことから、敬遠されやすい[要出典]

今日では、データが膨大になりやすい画像・音声・映像、それを格納するコンテナのフォーマットとして利用される傾向にある。

bmpは画像フォーマットの一つで、0x42 0x4dから始まる。

doc/xls/pptMicrosoft Officeで最もよく利用されていた利用されるファイルフォーマットである。

mp4は一般に映像や音声を格納するコンテナである。mp4そのものは映像フォーマットでも音声フォーマットでもないが、映像圧縮にh.264、音声圧縮にaacを使った(mp4(h.264/aac)と表記することが多い)mp4ファイルが広く流通しすぎた結果、mp4を映像フォーマットと勘違いしている人が多い。

zipはディレクトリ構造をファイルに変換すると同時に圧縮を行うことを想定しているファイルフォーマットである。もっとも圧縮は義務ではなく、複数の圧縮アルゴリズムから選択できる。一般的にはRFC 1951で規格化されたDEFLATEが圧縮アルゴリズムに採用されている。

また忘れてはいけないものとして、実行ファイルもある。Windowsなら.exeという拡張子がついている。

テキストファイルを特定のディレクトリ構造に配置したものをzip圧縮したファイルの種類

上記のようなバイナリファイルは扱いが難しいことから、複数のテキストファイルなどをzipで固めたものを独自のフォーマットとすることも多い。またこうすることで可搬性とデータサイズの小ささを両立しやすい。

こうしたファイルは特定の形式のファイルを特定のディレクトリ構造に配置したものをzipで圧縮していることを要求する。つまりzipファイルの特殊例(部分集合)である。

odt/ods/odp/odb/odg/odfはLibre Officeやその前身のOpenOfficeなどで最もよく利用されているファイルフォーマットである。実はMicrosoft Officeでも取り扱える。OpenDocument Formatと呼ばれ、ISO/IEC 26300などで標準化されている。

docx/xlsx/pptxOpenDocument Formatに対抗してMicrosoftが開発したファイルフォーマットでMicrosoft Officeで現在もっともよく利用されている。Office Open XMLと呼ばれECMA-376、ISO/IEC 29500:2008で標準化されている。

ただしここで大急ぎで補足しよう。

こう書くとバイナリファイルが2種類あると勘違いする人がいるかもしれないが断じて違う。
そもそも特定のディレクトリ構造をファイルに固めることができるファイルフォーマットはなにもzipだけではない(tarとかとか)。
zipファイルだって(一般的な意味での)バイナリファイルに含まれる
あえて数式で書けば、zip∈バイナリファイルだ。

ファイルはOSからどのように管理されるか

すこし話が変わる。FAT32とかNTFSとかext4とかという単語を聞いたことがあるだろうか?

私たちは普段ファイルを管理するのにディレクトリ(フォルダー)という概念を使っている。
これを実現しているのがさっき述べたようなディスクファイルシステムだ。

ファイル名や作成日時、更新日時、アクセス権限や、ディスクのどのセクターに書き込まれているかなどといった(例外はある)ことを管理している。

よく聞く拡張子とは、じつは単にファイル名の末尾から最初に現れた.までの文字列を(ただしファイル名の最初の文字が.の場合を除く)そう呼んでいるに過ぎない。

拡張子の役割

じつは何もない。むしろいらない。(大げさ)

しかし、人間がファイルを見てそれがどういう種類のファイルなのか判別するのにどうすればいいのかという問題がある。

バイナリファイルだけだったら、ファイルを解析して各フォーマットに固有なマジックナンバーを探すことで適切に処理ができる。それをパースして見せるソフトウェアがあれば人間はファイルの種類を判別できる。

しかしテキストファイルはそうはいかない。
xmlやhtmlのように冒頭で種類を宣言している場合はともかく、そうでない場合のほうが圧倒的多数である。

またファイルを解析するという作業は一度ファイルを開く必要があり、ディスクファイルシステムを見ればいいだけである拡張子をみる作業よりコストが高い。

そこで拡張子の出番だ。予めファイルの種類に対応する拡張子(かつてはアルファベット3文字程度が多かったが、しょっちゅう被るので最近は.vcxprojのように長いものが多い気がする)を利用者の間で決めておく。例えばWindowsでは実行ファイルはexecuteを省略して.exeを拡張子にするというコンセンサス(合意)がある。

C言語プリプロセッサとテキストファイルと拡張子

C言語を学習している人のために、もう一つ例を見てみよう。

御存知の通り、C言語ソースコード.cC言語のヘッダーファイルは.hという拡張子を主に使う。ここで

#include "a.h"

のようなソースコードC言語を学んだ人なら誰でも書いたことがあるだろうが、

#include "b.c"
#include "table.csv"

とかいうコードを見るととたんに頭がこんがらがる人いる。

これはC言語の入門書や入門サイト、はたまた学校での講義などが悪い。[要出典]

#includeはヘッダーファイルを読み込むときに使う構文ですよ。
ほら、#include <stdio.h>とかくとprintf関数が使えるでしょ?

などという嘘八百を平気で教えている。ひどいことにプリプロセッサが何かという説明すらしていないことも多い。
ハローワールドを教えた直後ならともかく、一通り教えたあとでもそんなことではお話にならないが、学校での講義や入門サイトはおおよそ例外なくそうなってしまう現状にあり[要出典]、入門書でも記述していなかったりする。
(なお独習Cは筆者にとっては残念なことに正しく解説がなされていたように思う)

C/C++にはプリプロセス時、コンパイル時、実行時の3つの世界が存在するが、

#includeとはこの内プリプロセス時にプリプロセッサによって解釈される構文だ。

機能としては、#include文を対象ファイルから消し去り、代わりに#include文で指定されたテキストファイルをその場所にコピペする、と言うものだ。

cpplover.blogspot.jp

[PDF] N4214: A Module System for C++ (Revision 2)

40年前のコピペ技術である#inludeにかわるモジュール機能の提案。

現在、C++が使っているコンパイルとリンクモデルは、C言語から受け継いだものである。各翻訳単位は、他の翻訳単位のことを一切知らずに処理される。翻訳単位を超えるには、名前リンケージ(シンボル名)を使う。名前に対応する定義はひとつだけでなければならない(ODR)。

翻訳単位の外に見える名前である外部名を、手動によるコピペで管理することを防ぐために、我々は40年前の技術である自動コピペ技術、プリプロセッサーによるヘッダーファイルを未だに使っている。これは、コンパイラーからみれば、コピペである。

ヘッダーファイルの中身はマクロによって書き換わってしまうおそれがあり、誤りの元である。

変数や関数の宣言ぐらいしか書かれていなかった昔のC言語ならば、まだ十分に耐えられたのだが、モダンなC++では、ヘッダーファイルには実行されるコードが記述されている。コンパイラーは翻訳単位ごとに重複した内容を処理しなければならず、現代のC++コンパイル時間の増大の原因になっている。

ただの自動コピペ機能なのだからテキストファイルならなんでも読み込める。まあそのあとコンパイルエラーになるかどうかは別問題であるが、それはコンパイル時のお話だ。

csvプリプロセス時読み込みはcsvに多少細工をしておくことで

qiita.com

に書いたように意味のあることに使える。

拡張子とソフトウェアの関連付け

Windows

Windowsの場合、拡張子とソフトウェアの関連付けはOSレベルで提供されている。

www.atmarkit.co.jp

もっというとレジストリに関連付け情報が記録されている。

レジストリの直接編集によるファイルの拡張子と関連づけ - Glamenv-Septzen.net

Mac

知らず

Linux

ない。なぜならばユーザーはどうせ適切なコマンドにファイルを渡すのだから。

ただ、GUI操作においては、ダブルクリックしたら適切なソフトが立ち上がらないと不便なので、ファイラーが関連付け機能を提供している。

まとめ

すべてのファイルは厳密にはバイナリファイルと言える。
すべてのファイルはテキストファイルであるかそうでないか(一般的な意味でのバイナリファイル)で分類される。
つまり、一般的な意味でのバイナリファイルとテキストファイルは全てのファイルの部分集合である。
またバイナリファイルと言っても、特にzipファイルは特定のファイルが特定のディレクトリ構造をとる場合の特殊例に名前がついていることがある(ex. xlsx)
zipファイルは一般的な意味でのバイナリファイルの部分集合であり、xlsxファイルはzipファイルの部分集合と言える。

さまざまな角度から拡張子とファイルのフォーマットについて見てきた。それぞれが全く異なるものであることがご理解いただけただろうか?そうであれば幸いだし、そうでなくても調べるきっかけくらいにはなっただろう。