先日Twitterでどうにも りーま氏(@strimuer213p)に拡張子とファイルのフォーマットは関係ないということが伝えられなかったので、ちょっと記事にしてみる。
ファイルの種類
まずいろんなファイルを片っ端からバイナリエディタで開いてみて欲しい。
Windowsをお使いならFavBinEdit、Linuxをお使いならGHexなどがいいのではないか。もちろんそんなところで宗教戦争をするつもりはないので、好きなものを使えばいい。
おれはコマンドがいいというなら、od
コマンドがUnixにはあるし、GNU拡張のod
コマンドはasciiの表示もしてくれる。
さて、開いてみてわかる通り、つまるところファイルとはただ単にbyte列の羅列だ。
多くのバイナリエディタは16進数でbyte列を表示してくれるが、別に8進数で表示するもののあるし、単に見た目の問題だ。
つまりすべてのファイルは(厳密には)バイナリファイルだと言える。
これをとりあえず大きく2つに分類する。
テキストファイル
コンピュータで文字を扱うときにどうするかというと、特定のbyte列で文字を表す。文字をその特定のbyte列に直すことを、文字エンコード、と言ったりする。
この変換の規則が(広義での)文字コードである。
広義での、とぼかして書くのには理由があって、実際にはかなりややこしいことになっているのだが、
それはこの辺のサイトに説明を譲る。本題ではないからだ。
話を戻す。
とにかく、ある一つの文字コードによってエンコードされたbyte列のみで構成させるファイルをテキストファイルと呼ぶ。
(一般的な意味での)バイナリファイル
先ほど、
つまりすべてのファイルは(厳密には)バイナリファイルだと言える。
と書いたが、一般にバイナリファイルと言われて指すものはそうではなく、すべてのファイルからテキストファイルを除いたものである。
テキストファイルの種類
テキストファイルといってもさまざまな種類がある。 どういうことかというと、何らかのルールにしたがって羅列されたテキストファイル、というのがある。
例えばHTML
は、W3Cが定めたHTML規格に則って文字列を記述することで、例えばブラウザでいわゆるWebページを作れる。
Markdown
は一応CommonMarkが標準規格としてあって、それにしたがって記述すれば、適当なツールでHTMLに変換できる(GFMはなんなんだという話はさておき)。
xml
は特定のデータ集合をソフトウェア間でやり取りするときに用いられ、xml上でのデータ構造そのものに意味がある。
(一般的な意味での)バイナリファイルの種類
バイナリファイルの形式は千差万別である。 C言語の構造体をそのまま書きだしたものと思ってよく、したがってバイナリファイルを扱うには処理系依存との戦いが開催される(C言語規格書の処理系依存部分との戦いの他に、エンディアン(バイトオーダー)との戦いがある)。多くのバイナリファイルフォーマットはバイトオーダーについて規定している(していないフォーマットはバグっている)。 このポータビリティ(可搬性)の低さと、多くのプログラミング言語で扱いが難しいことから、敬遠されやすい[要出典]。
今日では、データが膨大になりやすい画像・音声・映像、それを格納するコンテナのフォーマットとして利用される傾向にある。
bmp
は画像フォーマットの一つで、0x42 0x4d
から始まる。
doc
/xls
/ppt
はMicrosoft 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
/pptx
はOpenDocument Formatに対抗してMicrosoftが開発したファイルフォーマットでMicrosoft Officeで現在もっともよく利用されている。Office Open XMLと呼ばれECMA-376、ISO/IEC 29500:2008で標準化されている。
ただしここで大急ぎで補足しよう。
僕はテキストファイルの方が2種類あると思ってたんですがバイナリファイルの方が2種類あるんですね…
— りーま(情報処理技術者試験強化月間) (@strimuer213p) 2017年2月26日
テキストファイルが2種類あると問題がある現象が出てきましたし、2種類じゃないと説明出来ることがあるのでどうなってるんだろうと思ってました…
こう書くとバイナリファイルが2種類あると勘違いする人がいるかもしれないが断じて違う。
そもそも特定のディレクトリ構造をファイルに固めることができるファイルフォーマットはなにもzipだけではない(tarとかとか)。
zipファイルだって(一般的な意味での)バイナリファイルに含まれる。
あえて数式で書けば、zip∈バイナリファイル
だ。
ファイルはOSからどのように管理されるか
すこし話が変わる。FAT32とかNTFSとかext4とかという単語を聞いたことがあるだろうか?
私たちは普段ファイルを管理するのにディレクトリ(フォルダー)という概念を使っている。
これを実現しているのがさっき述べたようなディスクファイルシステムだ。
ファイル名や作成日時、更新日時、アクセス権限や、ディスクのどのセクターに書き込まれているかなどといった(例外はある)ことを管理している。
よく聞く拡張子とは、じつは単にファイル名の末尾から最初に現れた.
までの文字列を(ただしファイル名の最初の文字が.
の場合を除く)そう呼んでいるに過ぎない。
拡張子の役割
じつは何もない。むしろいらない。(大げさ)
しかし、人間がファイルを見てそれがどういう種類のファイルなのか判別するのにどうすればいいのかという問題がある。
バイナリファイルだけだったら、ファイルを解析して各フォーマットに固有なマジックナンバーを探すことで適切に処理ができる。それをパースして見せるソフトウェアがあれば人間はファイルの種類を判別できる。
しかしテキストファイルはそうはいかない。
xmlやhtmlのように冒頭で種類を宣言している場合はともかく、そうでない場合のほうが圧倒的多数である。
またファイルを解析するという作業は一度ファイルを開く必要があり、ディスクファイルシステムを見ればいいだけである拡張子をみる作業よりコストが高い。
そこで拡張子の出番だ。予めファイルの種類に対応する拡張子(かつてはアルファベット3文字程度が多かったが、しょっちゅう被るので最近は.vcxproj
のように長いものが多い気がする)を利用者の間で決めておく。例えばWindowsでは実行ファイルはexecuteを省略して.exe
を拡張子にするというコンセンサス(合意)がある。
Windowsの実行可能ファイルの拡張子
— りーま(情報処理技術者試験強化月間) (@strimuer213p) 2017年2月26日
.exeは
execute:(命令を)実行する
のexeだったんですか…
C言語プリプロセッサとテキストファイルと拡張子
C言語を学習している人のために、もう一つ例を見てみよう。
御存知の通り、C言語のソースコードは.c
、C言語のヘッダーファイルは.h
という拡張子を主に使う。ここで
#include "a.h"
のようなソースコードはC言語を学んだ人なら誰でも書いたことがあるだろうが、
#include "b.c" #include "table.csv"
とかいうコードを見るととたんに頭がこんがらがる人いる。
これはC言語の入門書や入門サイト、はたまた学校での講義などが悪い。[要出典]
#include
はヘッダーファイルを読み込むときに使う構文ですよ。
ほら、#include <stdio.h>
とかくとprintf
関数が使えるでしょ?
などという嘘八百を平気で教えている。ひどいことにプリプロセッサが何かという説明すらしていないことも多い。
ハローワールドを教えた直後ならともかく、一通り教えたあとでもそんなことではお話にならないが、学校での講義や入門サイトはおおよそ例外なくそうなってしまう現状にあり[要出典]、入門書でも記述していなかったりする。
(なお独習Cは筆者にとっては残念なことに正しく解説がなされていたように思う)
C/C++にはプリプロセス時、コンパイル時、実行時の3つの世界が存在するが、
#include
とはこの内プリプロセス時にプリプロセッサによって解釈される構文だ。
機能としては、#include
文を対象ファイルから消し去り、代わりに#include
文で指定されたテキストファイルをその場所にコピペする、と言うものだ。
[PDF] N4214: A Module System for C++ (Revision 2)
40年前のコピペ技術である#inludeにかわるモジュール機能の提案。
現在、C++が使っているコンパイルとリンクモデルは、C言語から受け継いだものである。各翻訳単位は、他の翻訳単位のことを一切知らずに処理される。翻訳単位を超えるには、名前リンケージ(シンボル名)を使う。名前に対応する定義はひとつだけでなければならない(ODR)。
翻訳単位の外に見える名前である外部名を、手動によるコピペで管理することを防ぐために、我々は40年前の技術である自動コピペ技術、プリプロセッサーによるヘッダーファイルを未だに使っている。これは、コンパイラーからみれば、コピペである。
ヘッダーファイルの中身はマクロによって書き換わってしまうおそれがあり、誤りの元である。
変数や関数の宣言ぐらいしか書かれていなかった昔のC言語ならば、まだ十分に耐えられたのだが、モダンなC++では、ヘッダーファイルには実行されるコードが記述されている。コンパイラーは翻訳単位ごとに重複した内容を処理しなければならず、現代のC++のコンパイル時間の増大の原因になっている。
ただの自動コピペ機能なのだからテキストファイルならなんでも読み込める。まあそのあとコンパイルエラーになるかどうかは別問題であるが、それはコンパイル時のお話だ。
csvのプリプロセス時読み込みはcsvに多少細工をしておくことで
に書いたように意味のあることに使える。
拡張子とソフトウェアの関連付け
Windows
Windowsの場合、拡張子とソフトウェアの関連付けはOSレベルで提供されている。
もっというとレジストリに関連付け情報が記録されている。
レジストリの直接編集によるファイルの拡張子と関連づけ - Glamenv-Septzen.net
Mac
知らず
Linux
ない。なぜならばユーザーはどうせ適切なコマンドにファイルを渡すのだから。
ただ、GUI操作においては、ダブルクリックしたら適切なソフトが立ち上がらないと不便なので、ファイラーが関連付け機能を提供している。
まとめ
すべてのファイルは厳密にはバイナリファイルと言える。
すべてのファイルはテキストファイルであるかそうでないか(一般的な意味でのバイナリファイル)で分類される。
つまり、一般的な意味でのバイナリファイルとテキストファイルは全てのファイルの部分集合である。
またバイナリファイルと言っても、特にzipファイルは特定のファイルが特定のディレクトリ構造をとる場合の特殊例に名前がついていることがある(ex. xlsx
)
zipファイルは一般的な意味でのバイナリファイルの部分集合であり、xlsxファイルはzipファイルの部分集合と言える。
さまざまな角度から拡張子とファイルのフォーマットについて見てきた。それぞれが全く異なるものであることがご理解いただけただろうか?そうであれば幸いだし、そうでなくても調べるきっかけくらいにはなっただろう。