GCCでUTF-8なソースの文字列リテラルをShift-JISのバイナリにコンパイルする

やりたいこと

UTF-8で保存されたC言語のソースファイル中の日本語文字列リテラルを、Shift-JIS(正確にはCP932)のバイナリにコンパイルしたい。使用するCコンパイラGCCとする。

なぜやりたいか

今どきはソースファイルはUTF-8エンコードで保存するのが一般的である。(MicrosoftVC++はいまだにCP932がデフォルトだが、それについてはこちらの記事を参照)

しかし、組込み系などではいまだ CP932 ベースの日本語文字列処理のライブラリが使われていることもあり、コンパイル時には日本語文字列リテラルはCP932のバイナリに変換したい。

やりかた

GCCのオプションで -fexec-charset=CP932 を指定する。

例えば、下記のようなC言語のソースをUTF-8で保存する。

#include <stdio.h>

int main(void)
{
    char hoge[] = "あいうえお";
    
    for(int i = 0; i < sizeof(hoge); i++)
    {
        printf("%02X ", (unsigned char)hoge[i]);
    }
    printf("\n");
}

これをふつうにGCCでビルドして実行すると、下記のような出力結果になる。当然ながら文字列リテラルUTF-8エンコードされている。

E3 81 82 E3 81 84 E3 81 86 E3 81 88 E3 81 8A 00

そこで、下記のようなオプション指定でコンパイルする。(ここで hoge.c はソースファイル名、hoge は実行ファイル名だとする。)

gcc -fexec-charset=CP932 hoge.c -o hoge

これを実行すると、下記のような出力結果になる。文字列リテラルがCP932でエンコードされていることが分かる。

82 A0 82 A2 82 A4 82 A6 82 A8 00

ちなみに

-fexec-charset オプションの指定がなければ、文字列リテラルはソースファイルでエンコードされている通りのバイナリにコンパイルされる。よってソースファイルがCP932であればバイナリもCP932になる。-fexec-charset=CP932 を指定すれば、ソースファイルのエンコードが何であれ、CP932に変換されたバイナリになる。

これが上手いやりかたか?

コンパイルオプションでの指定というのは少しうれしくない。現代ではIDEで開発することがふつうだし、IDEが変わればコンパイルオプションの設定方法が違うので、つぶしが効きにくい。

代案として、日本語文字列リテラルを含むソースファイルのみCP932で保存する、という逃げ方がある。しかしこれはこれで文字化けなどのデメリットがある。

ダメ文字問題に関しては、GCCでは -finput-charset=CP932 を指定すれば回避できる。これはソースファイルがCP932であることをコンパイラに明示するオプションである。しかしこの場合は文字列リテラルUTF-8に変換されてしまうので、-fexec-charset=CP932 を併用する必要があり、今回のケースでは意味が無い。

また、どちらにしても、一つのソースファイル内でUTF-8の文字列リテラルとCP932の文字列リテラルを混在はできない。(そんな必要性があるケースはごく稀だと思うが。)

ダメ文字問題の例

下記のソースではコメント行の末尾にダメ文字「能」があるため、その次の行もコメント扱いになってしまい、printf が実行されなくなる。

#include <stdio.h>

int main(void)
{
    char hoge[] = "あいうえお";
    
    for(int i = 0; i < sizeof(hoge); i++)
    {
        // ダメ文字→能
        printf("%02X ", (unsigned char)hoge[i]);
    }
    printf("\n");
}

参考