f

2015-01-17

C言語での日本語ファイル名でのファイル出力方法

C言語で日本語のファイル名でファイルを出力しようとすると,WindowsかLinuxのどちらかで文字化けする。この対処方法について説明する。結論としては,Windowsでのgccのコンパイルオプションに--exec-charset=sjisを追加すれば解決する。

以下の内容をもつアウト.datというファイルを出力するプログラムを考える。

アウト

名前をout.cとして,ソースコードを以下に示す。

// (File name: out.c)

#include <stdio.h>

int main(){
  FILE *fp;
  fp = fopen("./アウト.dat", "w");
  fprintf(fp, "アウト\n");
  fclose(fp);
  return 0;
}

このプログラムを以下のコマンドでコンパイルして実行する。

gcc -o out.exe out.c
./out.exe

Linux環境(Ubuntu 14.04)では問題なくアウト.datが出力される。しかし,Windows環境(MSYS2のMinGW-w64のgcc)でコンパイルして実行するとファイル名が文字化けする。ファイル内容は文字化けしない。

fopen関数の第1引数に日本語を渡すと環境に依存するようだ。日本語でファイル出力したい場面があるので可能ならば,環境に依存せずに日本語ファイル名を出力したい。

日本語ファイル名の出力方法

Windowsであればwindows.hに定義されている_wfopen関数を使えば日本語でも問題なく書き込める。しかし,この方法はWindows環境に依存する。マルチプラットホームでの動作を考えるならば使えない。標準のC言語のライブラリでどうにかできないか調べた。

調べた限りそのような関数はないようだ。

参考:streamのファイル名 - meryngii.neta http://meryngii.hatenablog.com/entry/20081130/1228044065

標準のC言語のソースコードの範囲では無理だとわかった。コンパイルオプションでどうにかできないか調べていると以下のサイトで記述を見つけた。

参考:猫科研究所 - gcc, windresで日本語を扱う方法 http://up-cat.net/gcc%252C%2Bwindres%25A4%25C7%25C6%25FC%25CB%25DC%25B8%25EC%25A4%25F2%25B0%25B7%25A4%25A6%25CA%25FD%25CB%25A1.html

以下のオプションにより実行ファイルの内部文字コードをShift JISに変換すればうまくいくようだ。

--exec-charset=sjis

このオプションの値はiconvコマンドが認識できるものならなんでもよいとのこと。つまり,sjisでもcp932でも同じShift JISの文字コードを指す。実際にWindowsで以下のようにこのオプションをつけてコンパイルして実行すると,ファイル名が文字化けしなかった。

gcc -o out.exe out.c --exec-charset=sjis

ただし,このオプションをつけると以下2点の問題がある。

  1. 日本語を含むファイルはファイルの文字コードもShift JISになる。
  2. Linux環境で同じオプションをつけて実行するとファイル名が文字化けする。

1について説明する。出力ファイルに日本語が含まれるとそのファイルの文字コードがShift JISになる。しかし,日本語が含まれなければUTF-8となる。この問題は以下2点の理由により解決は諦める。

  • 文字化けせずに表示されることが大事
  • きちんと出力できていればエンコードは別の方法で変換したり対応可能

2について説明する。同じout.cを--exec-charset=sjisオプションをつけてLinux環境でコンパイル・実行するとファイル名が文字化けする。元々LinuxはUTF-8がデフォルトなのでOSが想定しているエンコードと違うために起こるのだろう。

MakefileによるOSごとのオプションの自動設定

環境ごとにコンパイルオプションを考えないといけないのは面倒だ。また,実際にプログラムを作るときは複数のソースコードになるので,Makefileを書くのが一般的だ。そこで,OSごとのコンパイルオプションMakefileで吸収することにする。こうすれば,元々のソースコードを書くときやコンパイルするときにいちいち考えなくて済む。以下にMakefileを示す。

# (File name: Makefile)

## choose target compiler
FC = gfortran
CC = gcc
CXX = g++

## unset non target compiler
FC =
# CC =
CXX =

COMPILER = $(FC) $(CC) $(CXX)

## global flag (compile option) and library
LDFLAGS = -g -MMD -MP -mcmodel=large -Wall

TARGET = out.exe

ifdef FC
 SRC := $(wildcard *.f90)
 OBJ := $(SRC:%.f90=%.o)
 DEP := $(SRC:%.f90=%.d)
 MOD := $(wildcard *.mod)
 FFLAGS += -cpp
endif
ifdef CC
 SRC := $(wildcard *.c)
 OBJ := $(SRC:%.c=%.o)
 DEP := $(SRC:%.c=%.d)
 CFLAGS += -lm
endif
ifdef CXX
 SRC := $(wildcard *.cpp)
 OBJ := $(SRC:%.cpp=%.o)
 DEP := $(SRC:%.cpp=%.d)
 CXXFLAGS +=
endif

ifeq (${OS}, Windows_NT) # for Windows
 CFLAGS += --exec-charset=sjis
endif

all: ${TARGET}

${TARGET}: ${OBJ}
 ${COMPILER} -o $@ ${LDFLAGS} $^

%.o: %.f90
 ${FC} ${LDFLAGS} ${FFLAGS} -c $<
%.o: %.c
 ${CC} ${LDFLAGS} ${CFLAGS} -c $<
%.o: %.cpp
 ${CXX} ${LDFLAGS} ${CXXFLAGS} -c $<

clean:
 ${RM} ${TARGET} ${OBJ} ${DEP} ${MOD}

-include $(DEP)
.PHONY: all clean

上記のMakefileで重要なのは以下の3行だ。

ifeq (${OS}, Windows_NT) # for Windows
 CFLAGS += --exec-charset=sjis
endif

ここで,OSがWindowsであるときだけ--exec-charset=sjisのコンパイルオプションを追加している。これにより,OSがなんであっても日本語ファイル名でファイル出力するときに文字化けで悩まされずに済んだ。

0 件のコメント:

コメントを投稿