memset() によりデータが中止される
-
09-06-2019 - |
質問
呼び出し時に、コードの一部で奇妙な断続的なデータ異常終了 (確率 5% 未満) が発生します。 memset()
. 。問題は、通常、コードが数日間実行されない限りこの問題は発生しないため、実際にそれを見つけるのが難しいことです。
次のコードを使用しています。
char *msg = (char*)malloc(sizeof(char)*2048);
char *temp = (char*)malloc(sizeof(char)*1024);
memset(msg, 0, 2048);
memset(temp, 0, 1024);
char *tempstr = (char*)malloc(sizeof(char)*128);
sprintf(temp, "%s %s/%s %s%s", EZMPPOST, EZMPTAG, EZMPVER, TYPETXT, EOL);
strcat(msg, temp);
//Add Data
memset(tempstr, '\0', 128);
wcstombs(tempstr, gdevID, wcslen(gdevID));
sprintf(temp, "%s: %s%s", "DeviceID", tempstr, EOL);
strcat(msg, temp);
ご覧のとおり、最初に割り当てられていたサイズよりも大きなサイズの memset を使用しようとしていません。 malloc()
これの何が問題なのかわかる人はいますか?
解決
malloc
戻れる NULL
使用可能なメモリがない場合。あなたはそれをチェックしていません。
他のヒント
いくつかのことがあります。あなたが使っているのは sprintf
それは本質的に安全ではありません。バッファのサイズを超えないことが 100% 確実でない限り、ほぼ次のことを行う必要があります。 いつも 好む snprintf
. 。同じことが当てはまります strcat
;より安全な代替手段を好む strncat
.
明らかに、これでは何も解決しないかもしれませんが、 長さ これは、バグを見つけるのが非常に面倒になる可能性があるものを見つけるのに役立ちます。
malloc は、メモリがない場合に NULL を返すことができます。 利用できる。あなたはチェックしていません それ。
その通りその通り...メモリを監視していて十分な空きがあったので、そのことについては考えませんでした。malloc が失敗する以外に、システム上に使用可能なメモリがある方法はありますか?
はい、メモリが断片化されている場合は可能です。また、「メモリの監視」といっても、システム上に時折大量のメモリを消費し、気づかないうちにメモリを解放する何かがある可能性があります。お電話の場合は、
malloc
が発生すると、使用可能なメモリがなくなります。-- ジョエル
いずれにせよ...そのチェックを追加します:)
wcstombs
宛先のサイズを取得できないため、理論的にはバッファ オーバーフローが発生する可能性があります。
そしてなぜ使っているのか sprintf
定数だと私が想定しているものは何でしょうか?使用するだけです:
EZMPPOST" " EZMPTAG "/" EZMPVER " " TYPETXT EOL
C および C++ は、文字列リテラル宣言を 1 つの文字列に結合します。
Valgrindを使ってみましたか?通常、これがこの種のエラーをデバッグする最も速くて簡単な方法です。割り当てられたメモリの範囲外で読み取りまたは書き込みを行っている場合は、フラグが付けられます。
sprintfを使用していますが、 本質的に安全ではありません。あなたが100%でない限り あなたがするつもりがないことを肯定的に バッファーのサイズを超えると、 ほとんどの場合、snprintfを優先するはずです。同じことが strcat にも当てはまります。を優先します。 より安全な代替strncat。
うん.....最近はもっぱら .NET を使っているので、古い習慣はなかなか消えません。おそらくそのコードは、私の時代より前に書かれた何かから引っ張ってきたのでしょう...
しかし、今後はそれらを使用しないようにします;)
それはあなたのコードではないかもしれないことはわかっています...メモリリークが発生する可能性のある他のプログラムが実行されていますか?
それはプロセッサかもしれません。一部の CPU は単一バイトをアドレス指定できないため、ワードまたはチャンク サイズで作業するか、ワードまたはチャンクにアラインされたデータに対してのみ使用できる命令を必要とします。
通常、コンパイラはこれらを認識して回避しますが、場合によっては、領域をバイトとして割り当ててから、それを構造体またはバイトより広いフィールドとしてアドレス指定しようとすると、コンパイラがそれを捕捉しないことがあります。ただし、プロセッサは後でデータ例外をスローします。
特殊なCPUを使用していない限り、このようなことは起こりません。たとえば、ARM9 はそれを実行しますが、i686 は実行しません。Windows Mobile というタグが付いているので、CPU に問題があるのかもしれません。
する代わりに malloc
に続く memset
, を使用する必要があります calloc
これにより、新しく割り当てられたメモリがクリアされます。それ以外は、ジョエルの言ったことに従ってください。
NBは他の回答からいくつかのコメントを借用し、全体に統合しました。コードはすべて私のものです...
- エラーコードを確認してください。例えば。利用可能なメモリがない場合、malloc は NULL を返すことができます。これにより、データが中断される可能性があります。
- sizeof(char) は定義により 1 です
- バッファオーバーランを避けるには、sprintf ではなく snprintf を使用してください
- EZMPPOST などが定数の場合、フォーマット文字列は必要ありません。STRING1 " " STRING2 " " STRING3 としていくつかの文字列リテラルを結合し、全体を strcat するだけです。
- 必要以上に多くのメモリを使用しています。
- 1 つの小さな変更を加えれば、そもそも memset を呼び出す必要がなくなります。何もない ここでは初期化がまったく必要ありません。
このコードは同じことを安全に実行し、より高速に実行され、使用するメモリが少なくなります。
// sizeof(char) is 1 by definition. This memory does not require zero
// initialisation. If it did, I'd use calloc.
const int max_msg = 2048;
char *msg = (char*)malloc(max_msg);
if(!msg)
{
// Allocaton failure
return;
}
// Use snprintf instead of sprintf to avoid buffer overruns
// we write directly to msg, instead of using a temporary buffer and then calling
// strcat. This saves CPU time, saves the temporary buffer, and removes the need
// to zero initialise msg.
snprintf(msg, max_msg, "%s %s/%s %s%s", EZMPPOST, EZMPTAG, EZMPVER, TYPETXT, EOL);
//Add Data
size_t len = wcslen(gdevID);
// No need to zero init this
char* temp = (char*)malloc(len);
if(!temp)
{
free(msg);
return;
}
wcstombs(temp, gdevID, len);
// No need to use a temporary buffer - just append directly to the msg, protecting
// against buffer overruns.
snprintf(msg + strlen(msg),
max_msg - strlen(msg), "%s: %s%s", "DeviceID", temp, EOL);
free(temp);