C++ コンパイル:文字化けの 50 の色合い

興味深いことに、文字は最初は数字を追跡する方法として発明されました。言葉はずっと後に登場しました。

コンピュータは数字が得意です。彼らが本当に理解しているのはそれだけです。そのため、テキストは、解釈され、意味を与えられる一連の数字として表されなければなりません。

テキストと見なされる任意の識別子と文字列リテラルが存在するコード.C++ のコンテキストでは、プログラムのテキストはコンパイル中にどのように解釈され、トランスコードされますか?

このプログラムを実行したいとしましょう:

#include <iostream>
int main() {
 std::cout << "Γειά σου Κόσμε";
}

おそらく、コンパイラは次のように認識します:

23696e636c756465203c696f73747265616d3e0a696e74206d
61696e2829207b0a202020207374643a3a636f7574203c3c20
22ce93ceb5ceb9ceac20cf83cebfcf8520ce9acf8ccf83cebc
ceb5223b0a7d0a

これらの数字は文字を表していますが、どの数字がどの文字を表しているのでしょうか?個々の文字を表すために使用されるバイト数は?

そこでエンコーディングの出番です。

エンコーディングとは、1 バイト以上のシーケンスを文字として理解できるものにマッピングする方法です。いくつかのニュアンスがあります。さまざまなエンコーディングがあります。

    <リ>

    一部のエンコーディングは、1 バイト (またはそれ以下) を一意の文字にマップします。つまり、ASCII や EBCDI など、途方もなく少ない数の文字を表すことができます。

    <リ>

    一部のエンコーディングは、固定数のバイト (多くの場合 2) を一意の文字にマップします。それでも、人々が使用するすべての文字を表すには不十分です。たとえば、UCS2 です。

    <リ>

    一部のエンコーディングには、1 文字あたりの可変バイト数があり、0(n) インデックス作成を犠牲にしてメモリ効率を高めます。これは、たとえば UTF-8 です。

わかりました、嘘をつきました。エンコーディングは文字にマップされません。 キャラクター 一部のエンコーディングはグリフ (基本的にはフォント ファイルへのインデックス) にマップされますが、より最近のエンコーディングは、文字または「文字」の一部に割り当てられた番号であるコード ポイントにマップされます。

いずれにせよ、各エンコーディングは文字セットにマップされます。これは、文字セットとエンコーディングを単純化するために表すことができます。

エンコードは 1 つの特定の文字セットにマップされますが、同じ文字セットを異なるエンコードで表すことができます。たとえば、ASCII はエンコードと文字セットの両方ですが、UTF-8 と UTF-16 は ユニコード 文字セット。

これらすべての定義は、Unicode 用語集で見つけることができます

私たちは 150 年以上にわたって機械用のテキストをエンコードしてきましたが、当時としては理にかなっている理由から、多くのエンコードを行っています。

250 以上が公式に登録されています。

物理ソース ファイルの文字

上のスニペットのエンコーディングはどうなっているのですか?そして、そこに問題があります:私たちにはわかりませんが、コンパイラにもわかりません.

エンコーディングは、テキストを構成する残りのバイト シーケンスには保存されません。エンコーディングは観察できるものではありません。

しかし、どのエンコーディングを使用してそれを作成したかを知らずに、その一連の数字を解釈することはおそらく不可能です.ちょうど、言語が話されていることを知らずに言語を解釈することはできません.(もちろん、あなたのようにエンコーディングなしでテキストを持つことはできません.言語のない言葉はあり得ません。)

もちろん、ユーザーに尋ねることもできます。ユーザーは知っているかもしれません (笑)。

GCC と MSVC の両方にそのためのオプションがあります (-finput-charset/source-charset

特定のファイルに含まれるすべてのヘッダーが同じエンコーディングを共有している限り、これは機能します。サードパーティのライブラリを構成するファイルがどのようにエンコードされたか知っていますか?おそらくそうではありません.推測することもできます.これはコンパイラがデフォルトで行うことです.

Clang と GCC は、すべてが UTF-8 でエンコードされていると推測しますが、MSVC は、プログラムをコンパイルしているコンピューターのロケールからエンコードを取得します。

MSVC の仮定は、人々が自分のコードを共有しようとしない限り、特に別の国に住んでいる人々や別のオペレーティング システムを使用している人々と共有しようとしない限り、うまく機能します.しかし、なぜ誰もそれを行うのでしょうか?

お気付きかもしれませんが、ASCII エンコーディングに固執している限り、プログラムは正常にコンパイルされます。これは、UTF-8 を含むほとんどの 1 バイト エンコーディングが ASCII スーパーセットであるためです。そのため、ASCII 範囲のすべてのコードポイントに対して ASCII と同じマッピングが行われます。最大の例外は、IBM システムでのみ使用される EBCDIC です。Shift-JIS、 - 日本語 1 のエンコードに適したエンコード - いくつかの例外を除いて、ほとんど ASCII と互換性があります。

これが、ソース コードで非 ASCII 文字を避ける傾向がある最初の理由です。

しかし、本当にソース ファイルにギリシャ語を含めたい場合はどうすればよいでしょうか?まあ、GCC と clang は UTF-8 を想定しているため、既にそれをサポートしています。 /P>

まあ、それほど速くはありません。まず、下流のコードに責任を負わせ、適切なフラグを付けてコードをコンパイルします。そのため、必要な情報がいくつかあります コードのビルドはビルド システムにオフロードされますが、これはもろく、メンテナンスの負担になります。また、前述したように、コンパイラ フラグは翻訳単位で動作しますが、個々のファイルにエンコーディングを設定する必要があります。モジュールは、完全なモジュール式のようにすべてを解決します。世界 1 ファイル =1 翻訳単位。

それまでの間、Python のようにソース ファイルにエンコーディングを入れることはできますか?

#pragma encoding "UTF-8"
#include <iostream>
int main() {
 std::cout << "Γειά σου Κόσμε";
}

is にはいくつかの問題があります。まず、EBCDIC エンコーディングではまったく機能しません。EBCDIC として解釈される場合、上記の UTF-8 ファイルは次のようになります。

?/_/?>?>??????>%??/_??>?_/>???#???????????l?ce?c???'?\

私には C++ とは思えません。

では、EBCDIC2 は気にしません。 、これらのシステムで作業している人々はすでにすべてをトランスコードする必要があるため.UTF-8 であるすべての単一ファイルの先頭でそのディレクティブを使用できますか?

UTF-8 が適切なデフォルトであることを除いて、すべてのオープン ソース コードは UTF-8 であり、UTF-8 でのコンパイルは現時点では標準的な方法です。

だから人々に #pragma encoding "UTF-8" を書くように強制する コンパイラが UTF-8 が不適切なデフォルトであると想定するためです。

プラグマ (またはその他のメカニズム) で指定されていない限り、コンパイラに UTF-8 を想定させることができるのではないでしょうか?そうすると、一部のコードが壊れてしまいます。コードベース全体を任意のエンコーディングから UTF-8 に再エンコーディングすることは、ほとんどの場合操作を壊すことなく、簡単にできるはずですが、皮肉なことに、一部のエンコーディング テスト コードが壊れる可能性があります。

それにもかかわらず、デフォルトで UTF-8 を想定していない言語はほとんどありません。ただし、もちろん C++ を除きます。また、同じ言語を話すすべてのコンパイラがすぐに利益を得るため、UTF-8 が必要になりつつあります。

まず、UTF-8 文字列 const char8_t * = u8"こんにちは世界"; MSVCas const char8_t * = u8"ã“ã‚“ã«ã¡ã¯ä¸–ç•Œ"; によって解釈される可能性があります 米国や西ヨーロッパの多くの Windows マシンで。

私たちが望んでいるものではありません。

もちろん u8 文字列リテラルは UTF-8 の文字列ではなく、ソース エンコーディングから UTF-8 に変換される文字列です。これは紛らわしく、移植性がありません。

しかし、もちろんそれはさらに悪いことです。一部のコンパイラは、標準でサポートされている基本的なソース文字セット以外のコードポイントで構成される識別子を受け入れます3 .

これは興味深い質問を提起します:

  • これらのシンボルを移植できるようにマングルできますか?
  • これらのシンボルをポータブルに反映できますか?

システムのすべての部分が UTF-8 を想定して生成していない場合、結果は一貫性がなく、移植性がありません。

委員会が何を選択するかはわかりませんが、少なくとも実装者とユーザーを穏やかに UTF-8 ソース ファイルに移行させる方法を見つけることを願っています.

これは問題の半分でもありません.これまでのところ、ソースを内部エンコーディングに変換しただけです.これは指定されていませんが、Unicodeであると考えることができます.したがって、内部的に、コンパイラは任意のコードポイントを表すことができます.すばらしい。

u8u そして U 文字リテラルと文字列リテラルはそれぞれ UTF-8、utf-16、utf-32 に変換されます。これはロスレス操作です。

したがって、UTF-8 ソース ファイル内に u8 リテラルがある場合、それは変更されずにプログラム メモリに格納されます。ただし、これは標準によって実際に保証されているわけではありませんが、実装によって、たとえば Unicode 文字列を正規化できます。すばらしい!

しかし、char もあります と wchar_t リテラル。ここから物事が本当に崩壊し始めます。

したがって、すべての文字列を何かにエンコードする必要があることを覚えておいてください .しかし、何が?C++ は、プログラムが実行されるコンピューターのオペレーティング システムで使用されると思われるエンコーディングですべてのリテラルをエンコードします。

ほとんどのコンパイラにはそのためのオプションがありますが、デフォルトでは、実装はこれがコンパイラが実行されている環境のロケールから派生したものと同じエンコーディングであると想定します。

これは実行エンコーディングです .

推定実行エンコーディング

もちろん、より深い仮定は、インターネットが存在しないか、すべての人がすべて同じロケールを持っているということです4 またはエンコーディングごとにバイナリがあります。

もちろん、これはほとんどの linux/OSX/Android システムでうまく機能します。これは、すべてのコンポーネントが UTF-8 を使用するためです。コンパイラはリテラルを UTF-8 に変換し、実行時に UTF-8 として解釈します。

もう一方の端で MSVC を使用すると、デフォルトでの実行エンコーディングは、Windows の構成方法に依存します。これは基本的に、どこに住んでいるかによって異なります。

これらすべてが興味深い課題を提起しています…

  • Unicode から非 Unicode への変換は損失を伴う可能性があります。実装は診断を発行する必要がなく、MSVC は喜んでフロアに文字を落とします5 。 一方、GCC はそれを不正な形式にします。
  • もちろん、コードがコンパイルされるマシンが実行されるマシンと一致するという仮定は、現実を説明するものではありません.
  • 推定実行エンコーディングは公開されていないため、使用できる変換関数は、C および C++ 標準で提供されている便利なものだけです。

プログラムを実行しますか?

実行時に、プログラムは iostream などの標準機能に直面します。 環境が期待または生成すると思われるものに (codecvt や locale などの素晴らしいインターフェイスを使用して) テキストを (大まかに) トランスコードする可能性があります。

さらに悪いことに、表示したいがエンコーディングがわからない文字列 (制御できないシステムの一部から来ているため)、または単純にテキストではない文字列 - たとえば、パスが考慮されている一部のプラットフォームでは、表示できないバイトの袋です。

そしてもちろん、多くのシステムは UTF-8 を生成しますが、UTF-8 でなければナロー エンコーディングで変換することはできず、データの損失につながります。つまり、意味があります。

残念ながら、その環境を制御するためにできることは何もないため、標準は多少制限されています.

Windows ユーザーは、次の組み合わせのおかげで、プログラム内で正常に動作する UTF-8 文字列を簡単に使用できるようになったことを喜ぶことができます:

  • /utf8 MSVC のオプション
  • フォントの可用性に応じて、Unicode コードポイントの全範囲をサポートできるはずの新しい Windows 端末。
  • システム API で UTF-8 をサポートするための継続的な作業 - wchar_t の必要性を軽減 .

これがどのように機能するかを説明するプロジェクトに取り組み始めました。

これでは、EBCDIC プラットフォームとレガシー コードベースの問題は解決しません。

残念ながら、標準が非 Unicode エンコーディングからすぐに現実的に移行できるようには見えず、アロー リテラルとワイド リテラルはここにとどまります。

したがって、テキストを適切にサポートするには、標準で char8_t を追加する必要がある場合があります。 I/O からリフレクション、DNS など、テキストを扱う標準的な機能へのオーバーロード。

<locale> にパッチを当てる価値はないと思います または <iostream> というのは、それらが設計された前提が単にもはや有効ではないからです。また、非常に多くのコードがそれらに依存しているため、それらを非推奨にしようとする価値もないと思います.

それが教育の観点からどのように展開するかを見るのは興味深いでしょう。それにもかかわらず、その重複はおそらく必要悪です。改良された Unicode は最終的に Python 3 につながるものであり、C++ ではそれを避けたいと思うかもしれません.

<オール>
  • 「適切」の非常に大まかな定義について。 Shift-JIS は、日本語の文字の 10% 強しかエンコードできません。 ↩︎

  • C++ は現在、ほとんどが ASCII 中心の言語です ↩︎

  • A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
    0 1 2 3 4 5 6 7 8 9
    _ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '
    
    ↩︎
  • ロケールとエンコーディングが結びついているという考えはそもそもばかげているので、それを書くのは痛い.しかし、これらの仮定は70年前に行われたことを思い出してください. ↩︎

  • 私はそれを不自然にすることを望んでいます。 ↩︎