なんか人気のfopen_s関数について
前に書いた記事のPV伸びすぎなのと、最近fopen_s関数の存在を思い出したので改めてfopen_s関数について記事を書きます。
ちなみに前のfopen_s関数の記事は大学(.ac.jpドメイン)からのアクセスが結構あるので、fopen関数の代わりにfopen_s関数を教えている大学はそれなりに存在することが推測できます。
fopen_s関数について
fopen_s関数はC11で標準Cライブラリに取り入れられた関数です。
基本的にはfopen関数と同じで、ファイルを開くという役割も変わっていません。
ただし、戻り値と引数は変わっているので単純に置換することはできません。
実はfopen_s関数自体はC11以前からMicrosoft Visual C++(MSVC)の独自拡張で存在していました。
MSVCの独自拡張が標準化されたという感じです。
C11で標準化され、MSVC以外でも使えそうなfopen_s関数ですが、fopen_s関数の実装は任意です。
処理系によってはfopen_s関数は存在しません。
例えばglibcはfopens_s関数を実装していません。
fopen_s関数の使い方
まずは引数と戻り値から。
1 2 3 4 |
FILE *fopen(const char *filename, const char *mode); errno_t fopen_s(FILE **fp, const char *filename, const char *mode); |
fopen_s関数はファイルポインタのアドレスを引数に取り、戻り値がerrno_t
になっています。
ファイルオープンに成功した場合の戻り値は0です。
ファイルオープンに失敗すればfopen関数同様にファイルポインタは空ポインタ(NULL
)になり、errnoが戻り値になります。
fopen_s関数もそうですが、C11から追加された末尾に_sが付いている関数の実装は任意で、実装されている処理系では__STDC_LIB_EXT1__
マクロが定義されます。
使うには該当する関数のヘッダファイルをincludeする前に__STDC_WANT_LIB_EXT1__
マクロを1にする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> int main(void) { FILE *fp; #ifdef __STDC_LIB_EXT1__ fopen_s(&fp, "test.txt", "r"); #endif ... return 0; } |
これはすでに_s系の関数があるMSVCで関数名が衝突しないようにするためだそうです。
__STDC_WANT_LIB_EXT1__
マクロは0だと_s系の関数が宣言・定義されません。
fopen_sとfopenの違い
引数と戻り値以外にも違いがあります。
しばしば「セキュリティが強化されている」とだけ説明されますが、あまりにも抽象的すぎる説明です。
戻り値でエラーの種類が分かるからセキュリティが〜という説明も見るんですけど多分違います。
fopen関数でもerrnoで取得できますからね。
fopen_sとfopenの最大の違いはファイルを排他モードで開くかどうかです。
例えば以下の2つのコードはfopen_sとfopenの違いしかありませんが、異なる結果になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> int main() { FILE *fp1; FILE *fp2; if (fopen_s(&fp1, "test.txt", "w") == 0) { puts("fp1 != NULL"); } else { puts("fp1 == NULL"); } if (fopen_s(&fp2, "test.txt", "r") == 0) { puts("fp2 != NULL"); } else { puts("fp2 == NULL"); } if (fp1) fclose(fp1); if (fp2) fclose(fp2); return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> int main() { FILE *fp1; FILE *fp2; if ((fp1 = fopen("test.txt", "w")) != NULL) { puts("fp1 != NULL"); } else { puts("fp1 == NULL"); } if ((fp2 = fopen("test.txt", "r")) != NULL) { puts("fp2 != NULL"); } else { puts("fp2 == NULL"); } if (fp1) fclose(fp1); if (fp2) fclose(fp2); return 0; } |
動作結果
http://rextester.com/JMGR52902
http://rextester.com/KOZ39339
C11のfopen_sはファイルモードのwの前にuを加えると共有モードでファイルを開けますが、MSVCの実装はC11に準拠していないので使えません。
まあxも使えないんですけど。
fopen_s関数が使われる理由
ここでとても単純なソースコードを載せます。
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdio.h> int main(void) { FILE *fp; if ((fp = fopen("text.txt", "r")) != NULL) { fclose(fp); } return 0; } |
ファイルを開いて、閉じるだけで何もしません。
これをVisual Studio 2015とそれに付いているVisual C++でコンパイルします。
gccやclangだとコンパイルが通るにも関わらず、コンパイルに失敗します。
1 2 |
error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. note: 'fopen' の宣言を確認してください |
つまり、fopen_s関数が使われる理由はVisual Studioでエラーになるからです。
ちなみに直接clコマンドを使ってコンパイルするとエラーにはなりません。
Visual Studioでfopen_s関数を使わなくてもいい方法
_CRT_SECURE_NO_WARNINGS
マクロを毎回定義する
エラーメッセージにあるように_CRT_SECURE_NO_WARNINGS
マクロを定義するとC4996が無効化されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #endif #include <stdio.h> int main(void) { FILE *fp; if ((fp = fopen("text.txt", "r")) != NULL) { fclose(fp); } return 0; } |
SDLチェックを無効化する
MSDNを参照してください。
/sdl (追加のセキュリティ チェックの有効化)
https://msdn.microsoft.com/ja-jp/library/jj161081.aspx#Anchor_4
SDLチェックは規定で無効になっているそうですが、Visual Studioでプロジェクトを作ると有効にされています。
SDLチェックを無効化すると警告C4996がエラーにならなくなります。
プリプロセッサの設定を変更する
- メニューの「プロジェクト(P)」→プロパティ(P)でプロジェクトの設定を開く
- 構成プロパティ→C/C++→プリプロセッサ
- プリプロセッサの定義に「_CRT_SECURE_NO_WARNINGS」を追加する
セミコロン区切りなので注意
これで勝手にマクロが定義されます。
個人的な意見
インターネットではfopenを使うべきという人もいれば、fopen_sを使うべきという人もいます。
書籍だとほぼfopenの方を使っているのに、入門者によく勧められるVisual Studioだとfopenはエラーになります。
入門者なら間違いなく混乱するでしょう。
その混乱ぶりはYahoo!知恵袋を見れば明らかです。
https://chiebukuro.yahoo.co.jp/search/?p=fopen_s&flg=3&class=1&ei=UTF-8&fr=common-navi&dnum=2078297622
適当に「fopen_sの方が正しい」と教えるとgccやclangなどでコンパイルするときにエラーになって余計に混乱します。
絶対にやめてください。
現に僕の周りの人間が混乱してます。
C11でもfopen_sはoptionalだからな。
あと、ろくに調べもせず安易な理由でにVisual StudioでC言語を教えないでください。
fopen関数使っただけでエラーになる訳あり統合開発環境であるということを理解して使ってください。
本当に頼みますよ。