sprintf、vsprintf、sprintf_s、vsprintf_s、_snprintf、_vsnprintf、snprintf、vsnprintf関数の識別



Sprintf Vsprintf Sprintf_s



タイトルの関数名を読んだ後、めまいがしますか?今後、このような細部の絡み合いを防ぐために、本日、これらの機能の類似点と相違点を分析するようになりました。

ラボ環境:



WindowsでVS2017を使用する
Linuxでgcc4.9.4を使用する

機能の安全性を検証するために、以下の構造を設計しました



画像

const int len = 4 #pragma pack(push) #pragma pack(1) struct Data { char buf[len] char guard Data() { for (int i = 0 i 

画像

Data.bufフィールドにデータを書き込むときに、メモリの範囲外の状況が発生すると、Data.guradフィールドのメモリが変更されます。これを使用して、関数の安全性を推測します。



1. sprintf(Linux / Windows)

Linuxでの関数プロトタイプ:int sprintf(char * str、const char * format、...)
テストコード:

画像

int main() { Data data data.Display() int ret = sprintf(data.buf, '%d', 12) std::cout << 'ret = ' << ret << std::endl data.Display() std::cin.get() return 0 }

画像

VS2017環境では、この機能は安全ではないとマークされています。使用されている場合、コンパイラは警告を報告します。これを使用する必要がある場合は、コンパイル時にマクロ定義_CRT_SECURE_NO_WARNINGSを追加して、セキュリティ警告を無視するようコンパイラに指示する必要があります。この関数は、Linuxでは通常使用できます。また、この関数はWindowsとLinuxで同じように動作します。詳細は次のとおりです。

1.ソースデータの長さが[len未満]の場合、sprintfはデータをターゲットメモリに完全に書き込み、テールが0で終わるようにし、書き込まれたバイト数を返します。現時点では、関数の動作は安全です。
例えば:

sprintf(data.buf、 '%d'、12)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 ---------------

画像

2.ソースデータの長さがlenに等しい場合、sprintfはデータをターゲットメモリに完全に書き込み、ターゲットメモリの最後に追加の0を書き込み、書き込まれたバイト数を返します。この時点で、関数は範囲外にコピーされています。したがって、割り当てられたメモリがコピー要求をちょうど満たしているとユーザーが考える場合、潜在的なリスクが実際に発生しています。

例えば:

sprintf(data.buf、 '%d'、1234)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 4 sizeof(Data) = 5 buf = 1234 guard = 0 memory has been broken. ---------------

画像

3.ソースデータの長さが[lenより大きい]場合、sprintfはデータをターゲットメモリに完全に書き込み、メモリの範囲外の状況に関係なく、書き込まれたバイト数を返します。エラーコードはそうではありません。戻ってきた。

例えば:

sprintf(data.buf、 '%d'、123456)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 6 sizeof(Data) = 5 buf = 123456 guard = 53 memory has been broken. ---------------

画像

概要:上記の3セットの実験結果は、WindowsとLinuxの両方で検証できます。 sprintf関数の安全率はほぼ0であることがわかりますので、すべての人が使用することはお勧めしません。
vsprintfはsprintfと同じように動作します。

2、sprintf_s(Windowsのみ)

sprintf関数の欠点を補うために、sprintf_s関数が上位バージョンのMSVC環境に導入されました。これは、呼び出し時にユーザーが渡すターゲットメモリの長さをサポートし、関数プロトタイプは次のように表すことができます。 :

int sprintf_s(char * buf、size_t buf_size、const char * format、...)

1.ソースデータの長さが[len未満]の場合、sprintfはデータをターゲットメモリに完全に書き込み、テールが0で終わるようにし、書き込まれたバイト数を返します。現時点では、関数の動作は安全です。
例えば:

sprintf_s(data.buf、len、 '%d'、12)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 ---------------

画像

2.ソースデータの長さが[等しい]または[より大きい] lenの場合、この関数を呼び出すとアサーションがトリガーされます。デバッグモードでは、ランタイムエラープロンプトボックスがポップアップ表示され、リリースモードではユーザーに「バッファが小さすぎます」と通知され、プログラムが直接クラッシュします。

例えば:

sprintf_s(data.buf、len、 '%d'、1234)

デバッグモードで実行すると、以下に示すように、アサートがトリガーされます。

概要:sprintf_s関数は、Windowsでのみ使用できます。メモリは損傷しませんが、アサートがトリガーされ、プログラムが中断されます。注意して使用する必要があります。
vsprintf_sはsprintf_sと同じように動作します。

3、_snprintf(Windowsのみ)

sprintf_sの安全性が十分でないためかもしれません。 _snprintfという名前の関数もMSVC環境に導入されています。その関数プロトタイプはsprintf_sに似ており、次のように表すことができます。

int _snprintf(char * buf、size_t buf_size、const char * format、...)

そのパフォーマンスは次のとおりです。
例1:ソースデータの長さが[len未満]の場合、完全に書き込むことができ、0で終わり、実際に書き込まれたバイト数が返されます。

_snprintf(data.buf、len、 '%d'、12)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 ---------------

画像

例2:ソースデータの長さが[等しい] lenの場合、最後に処理を行わなくても完全に書き込むことができ、実際に書き込まれたバイト数が返されます。

_snprintf(data.buf、len、 '%d'、1234)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 4 sizeof(Data) = 5 buf = 1234 guard = 15 ---------------

画像

例3:ソースデータの長さが[より大きい] lenの場合、最大で[len]文字を書き込むことができ、終わりは悪くありません。すべての処理が返されます。 [-1]:

_snprintf(data.buf、len、 '%d'、123456)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = -1 sizeof(Data) = 5 buf = 1234 guard = 15 ---------------

画像

概要:_snprintf関数は、Windowsでのみ使用できます。最大で[size]文字を書き込むことができ、メモリを破壊したり、割り込みをトリガーしたりすることはできませんが、ターゲットメモリが0で終わることを保証することはできません。戻り値を通じて、関数呼び出しが成功したかどうかを知ることができます。戻り値> = 0の場合、呼び出しが成功し、戻り値が-1の場合に実際に書き込まれた文字数が返されることを意味します。これは、ターゲットメモリが小さすぎて呼び出しが失敗することを意味しますが、私は充填を行うのが最善です。

_vsnprintfは_snprintfと同じように動作します。

4、snprintf(Linux / Windows)

Linuxでの関数プロトタイプは次のとおりです。

int snprintf(char * str、size_t size、const char * format、...)

この関数はWindowsとLinuxの両方で使用でき、動作は一貫しています。つまり、最大で[size-1]文字をターゲットメモリに書き込み、0で終わるようにします。戻り値は[実際に書き込まれたバイト数]ではなく、[書き込まれるバイト数]です。
例1:ソースデータの長さが[len未満]の場合、完全に書き込むことができ、0で終わり、実際に書き込まれたバイト数が返されます。

snprintf(data.buf、len、 '%d'、12)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 2 sizeof(Data) = 5 buf = 12 guard = 15 ---------------

画像

例2:ソースデータの長さが[equal] lenの場合、実際には[len-1]文字のみが書き込まれ、最後の文字は0で埋められますが、戻り値は[len]です。

snprintf(data.buf、len、 '%d'、1234)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 4 sizeof(Data) = 5 buf = 123 guard = 15 ---------------

画像

例3:ソースデータの長さが[より大きい] lenの場合、最大で[len-1]文字しか書き込むことができず、最後の文字は0で埋められますが、戻り値は[バイト数書かれるべきです]:

snprintf(data.buf、len、 '%d'、123456)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 6 sizeof(Data) = 5 buf = 123 guard = 15 ---------------

画像

概要:snprintf関数は、Linux / Windowsプラットフォームの両方で使用できます。最大[size-1]文字を書き込むことができ、メモリを破壊したり、割り込みをトリガーしたりすることはなく、ターゲットメモリが0で終わることを常に保証します。唯一の問題は、戻り値が信頼できないことであり、呼び出しが失敗したかどうかを推測します。

vsnprintfの動作はsnprintfと同じです。

この時点で、sprintfシリーズの関連機能はすべて終了しており、完全な機能はないようです。しかし、それらの特定の動作がわかったので、アプリケーションのシナリオに応じて適切な機能を選択できます。

補足:ここに書かれたので、この機会にstrcpy関数クラスターを研究しましょう。

テストコード:

画像

int main() { Data data data.Display() const char * ret = strncpy(data.buf, '12345678', len) std::cout << 'ret = ' << ret << std::endl data.Display() std::cin.get() return 0 }

画像

1つ、strcpy(Linux / Windows)
関数プロトタイプは次のとおりです。char* strcpy(char * dest、const char * src)
最も古い文字列コピー関数の原理は単純です。 0になるまで、ソース文字列からターゲットアドレスに文字を順番にコピーします。メモリのオーバーラップが発生した場合は、特別な処理が必要です。常に実際に書き込まれた文字数を返し、範囲外のメモリを処理せず、安全性も低いため、ここでは繰り返しません。


2、strcpy_s(Windowsのみ)
これはWindowsに固有の機能であり、プロトタイプは次のように説明できます。
int strcpy_s(char * dest、size_t size、const char * src)
戻り値はターゲット文字列の最初のアドレスではなく、intであることに注意してください。
ソース文字列の長さがターゲットメモリの[未満]または[等しい]場合、この関数は安全に実行でき、戻り値は[0]です。ソース文字列の長さがターゲットメモリよりも大きい場合、この関数はアサートアサーションをトリガーします。プログラムを中断させます。この関数はメモリ破損を引き起こしません。

3、strncpy_s(Windowsのみ)
これはWindowsに固有の機能であり、プロトタイプは次のように説明できます。
int strncpy_s(char * dest、size_t dest_size、const char * src、size_t count)
戻り値もintです。
この関数は、ターゲットメモリのサイズを指定するだけでなく、コピーする文字数も指定できます。これは、二重保護に相当します。
ただし、[カウント<= dest_size-1] must be satisfied, this function can be called correctly, otherwise it will trigger the assert interrupt.

4、strncpy(Linux / Windows)
関数プロトタイプ:char * strncpy(char * dest、const char * src、size_t size)
動作はstrcpyに似ています。 0になるか、ターゲットメモリがいっぱいになるまで、ソース文字列からターゲットアドレスに文字を順番にコピーし、最大[size]文字をコピーします。この関数は、メモリを損傷したり、プログラムを中断したりすることはありませんが、ターゲット文字列が0で終わることを保証することはできません。
例えば:

strncpy(data.buf, '12345', len)

出力:

画像

sizeof(Data) = 5 buf = ****hot guard = 15 --------------- ret = 1234 sizeof(Data) = 5 buf = 1234 guard = 15 ---------------

画像