こんにちは。田原です。
回はMicrosoftのVisual Stduioに含まれるVisual C++の話題です。
Visual C++を使っていてC4996警告に悩まされ時があると思います。その対策について解説します。
1.C4996警告がでる時
例えば、文字列を文字列へ繋げる処理はよくやると思います。
そのようなC言語ソースを下記のように作ってVisual StudioでビルドするとC4996警告が表示されます。(バッファオーバーフローに気を使っているので、ちょっと長いです。)
#include <stdio.h> #include <string.h> #include <stdbool.h> int main() { char buff[20]; char const* str="test string"; // 11文字 char const* add=" (added)"; // 8文字 bool is_error=true; int len=strlen(str); if (len < sizeof(buff)) { strcpy(buff, str); if (len+strlen(add) < sizeof(buff)) { strcat(buff, add); is_error=false; } } if (!is_error) { printf("buff='%s'\n", buff); } else { printf("%s\n", "error"); } return 0; }
1>blog20161126.c(45): warning C4996: ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
1>blog20161126.c(48): warning C4996: ‘strcat’: This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
strcpy()やstrcat()の代わりにstrcpy_s()やstrcat_s()を使うか、_CRT_SECURE_NO_WARNINGSを使って警告をディセーブルするよう書かれています。
2.確実な対策
そこで、strcpy_s()とstrcat_s()の解説を見て、下記のように修正すると警告は消えます。
#include <stdio.h> #include <string.h> #include <stdbool.h> int main() { char buff[20]; char const* str="test string"; // 11文字 char const* add=" (added)"; // 8文字 bool is_error=true; int len=strlen(str); if (len < sizeof(buff)) { strcpy_s(buff, sizeof(buff), str); if (len+strlen(add) < sizeof(buff)) { strcat_s(buff, sizeof(buff), add); is_error=false; } } if (!is_error) { printf("buff='%s'\n", buff); } else { printf("%s\n", "error"); } return 0; }
さて、strcpy_s()やstrcat_s()が何をしてくれるかというと、バッファオーバーフロー対策です。パラメータが不正にNULLだったり、バッファ・サイズを超えるようなコピーをしようとしたら、プログラムを停止させます。
このように頑張っても、元々バッファオーバーフロー対策ができていたら、意味はありません。
しかし、バッファオーバーフロー対策が不十分なことも考えられるため、使っておくと安心と思います。
3.手抜きな対策
しかし、単にC言語を勉強しているだけの人もいると思います。
C言語自身は小型CPUのファームウェアやlinuxのカーネル等、アチコチで使われていますが、そのような分野でC言語の標準ライブラリに頼るケースは、経験的にかなりレアと思います。例えば、小型CPUのファームウェア開発では、Cコンパイラを使いますが、標準ライブラリを使うような贅沢はなかなかできないです。
また、C言語しか使えない人より、C++も使える人の方が需要は多いですので、C言語をある程度マスターできたらC++へ進もうと考えている人も多いと思います。
そのような人にとって、設計が古く制約が多いC言語の標準ライブラリを使いこなすことに時間をかけることは惜しいと思います。(C言語の標準ライブラリを実務で実際に使うかどうか分からない時は特に。)
また、あなたのプログラムが使っているライブラリがstrcpy()等を使っているけど、そのライブラリの信頼性が十分に高い場合もあるでしょう。その場合、下手に修正するわけにはいきません。
そのような学習中のソフト(信頼する必要がない)や信頼できるソフトの場合は、エラー・メッセージにあるように_CRT_SECURE_NO_WARNINGSを定義して警告を表示させないのも手です。
ソース・コードの先頭で#defineします。
#define _CRT_SECURE_NO_WARNINGS // ★★★ ここ #include <stdio.h> #include <string.h> #include <stdbool.h> int main() { 以下略
ただ、この方法の場合、使っているライブラリ・ヘッダのインクルード時だけ警告をディセーブルするようなことなどができません。
#pragma warningを使えば可能です。更に区間を指定するならマクロを定義して使いたいです。
下記のようにマイクロソフト独自の__pragma(warning)を使えば、信頼できる部分だけC4996警告を禁止することができます。
ここではstrcat()周辺のみ警告をディセーブルしてます。
#include <stdio.h> #include <string.h> #include <stdbool.h> #ifdef _MSC_VER #define DISABLE_C4996 __pragma(warning(push)) __pragma(warning(disable:4996)) #define ENABLE_C4996 __pragma(warning(pop)) #else #define DISABLE_C4996 #define ENABLE_C4996 #endif int main() { char buff[20]; char const* str="test string"; // 11文字 char const* add=" (added)"; // 8文字 bool is_error=true; int len=strlen(str); if (len < sizeof(buff)) { strcpy(buff, str); DISABLE_C4996 // ★★★ ここ if (len+strlen(add) < sizeof(buff)) { strcat(buff, add); is_error=false; } ENABLE_C4996 // ★★★ ここ } if (!is_error) { printf("buff='%s'\n", buff); } else { printf("%s\n", "error"); } return 0; }
4.因みにC++なら
最後に、同様な処理をC++を使って実装したら下記のようになります。
オーバーフロー対策は標準ライブラリに任せることができるので安心です。バッファが不足したら自動的に拡張されるので、バッファ不足エラー・チェックも省けます。
#include <iostream> #include <string> int main() { std::string buff; char const* str="test string"; // 11文字 char const* add=" (added)"; // 8文字 buff=str; buff+=add; std::cout << buff << "\n"; return 0; }
C++は難しいと言われます。確かに全てを学ぼうとすると、本当に難しいです。でも、全てを使う必要は全くありません。多くのCコンパイラはC++もコンパイルできますので、使える部分だけ使えば良いのです。
そのようなC++の便利機能をここに纏めてますので参考にしてみて下さい。
当記事のアイコンで使用している上記のマークは米国 Microsoft Corporation およびその関連会社の商標です。