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点の問題がある。
- 日本語を含むファイルはファイルの文字コードもShift JISになる。
- 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がなんであっても日本語ファイル名でファイル出力するときに文字化けで悩まされずに済んだ。