予備観察
このテストコードを考えると:
static void chkit(char *s, char *u)
{
printf("[%s] && [%s]", s, u);
UpdateString(s, u);
printf(" ==> [%s]\n", s);
}
int main(void)
{
char MainString[200] = "";
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "XYZ");
chkit(MainString, "XYZ");
chkit(MainString, "XYZ");
chkit(MainString, "DEF");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "XYZ");
chkit(MainString, "ABC");
chkit(MainString, "GHI");
return 0;
}
私が得る出力は次のとおりです。
[] && [ABC] ==> [ABC=1]
[ABC=1] && [ABC] ==> [ABC=2]
[ABC=2] && [ABC] ==> [ABC=3]
[ABC=3] && [XYZ] ==> [ABC=3:XYZ=1]
[ABC=3:XYZ=1] && [XYZ] ==> [ABC=3:XYZ=2]
[ABC=3:XYZ=2] && [XYZ] ==> [ABC=3:XYZ=3]
[ABC=3:XYZ=3] && [DEF] ==> [ABC=3:XYZ=3:DEF=1]
[ABC=3:XYZ=3:DEF=1] && [ABC] ==> [ABC=4:XYZ=3:DEF=1]
[ABC=4:XYZ=3:DEF=1] && [ABC] ==> [ABC=5:XYZ=3:DEF=1]
[ABC=5:XYZ=3:DEF=1] && [ABC] ==> [ABC=6:XYZ=3:DEF=1]
[ABC=6:XYZ=3:DEF=1] && [ABC] ==> [ABC=7:XYZ=3:DEF=1]
[ABC=7:XYZ=3:DEF=1] && [ABC] ==> [ABC=8:XYZ=3:DEF=1]
[ABC=8:XYZ=3:DEF=1] && [ABC] ==> [ABC=9:XYZ=3:DEF=1]
[ABC=9:XYZ=3:DEF=1] && [ABC] ==> [ABC=10:=3:DEF=1]
[ABC=10:=3:DEF=1] && [XYZ] ==> [ABC=10:=3:DEF=1:XYZ=1]
[ABC=10:=3:DEF=1:XYZ=1] && [ABC] ==> [ABC=11:3:DEF=1:XYZ=1]
[ABC=11:3:DEF=1:XYZ=1] && [GHI] ==> [ABC=11:3:DEF=1:XYZ=1:GHI=1]
数字が1桁から2桁に増えると、明らかに問題が発生します。
1つの問題
コード内:
if (nIsPresentFlag == 1)
for (j = 0; j < i; j++)
sprintf(workbuf, "%s%s=%s:", workbuf, pData[j][0], pData[j][1]);
workbuf
パラメータの1つとして書き込み、渡すことにより、未定義の動作を呼び出しています。それは単に危険です。あなたがそれで逃げる可能性は中程度ですが、「逃げる」は有効な用語です—それが機能するという保証はありません。
新しい番号を不十分なスペースにフォーマットすると、上書きの問題が発生します。
作業コード
以下のコードは機能しているようです。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
static void UpdateString(char *MainString, char ToUpdate[20])
{
char *pData[50][2];
char *saveptr1 = NULL;
int i = 0;
int nIsPresentFlag = 0;
char workbuf1[200];
char workbuf[200];
char extra[16];
if (strlen(MainString) > 0)
strcat(MainString, ":");
strcpy(workbuf1, MainString);
pData[i][0] = strtok_r(workbuf1, "=", &saveptr1);
pData[i][1] = strtok_r(NULL, ":", &saveptr1);
if (pData[i][0])
i++;
while ((pData[i][0] = strtok_r(NULL, "=", &saveptr1)) != 0)
{
pData[i][1] = strtok_r(NULL, ":", &saveptr1);
i++;
}
for (int j = 0; j < i; j++)
{
if (strncmp(ToUpdate, pData[j][0], strlen(pData[j][0])) == 0)
{
unsigned int CdrCnt = atoi(pData[j][1]);
CdrCnt += 1;
pData[j][1] = extra;
sprintf(pData[j][1], "%u", CdrCnt);
nIsPresentFlag = 1;
break;
}
}
if (nIsPresentFlag == 1)
{
char *dst = workbuf;
for (int j = 0; j < i; j++)
{
int n = sprintf(dst, "%s=%s:", pData[j][0], pData[j][1]);
/* Broken if sprintf() returns -1 */
dst += n;
}
}
else
sprintf(workbuf, "%s%s=%d:", MainString, ToUpdate, 1);
workbuf[strlen(workbuf)-1] = '\0';
strcpy(MainString, workbuf);
}
static void chkit(char *s, char *u)
{
printf("[%s] && [%s]", s, u);
UpdateString(s, u);
printf(" ==> [%s]\n", s);
}
int main(void)
{
char MainString[200] = "";
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "XYZ");
chkit(MainString, "XYZ");
chkit(MainString, "XYZ");
chkit(MainString, "DEF");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "ABC");
chkit(MainString, "XYZ");
chkit(MainString, "ABC");
chkit(MainString, "GHI");
chkit(MainString, "PQRSTU");
chkit(MainString, "I");
chkit(MainString, "I");
chkit(MainString, "I");
chkit(MainString, "PQRSTU");
return 0;
}
操作は省略されmemset()
ます。nullで終了する文字列は任意のデータにコピーでき、nullターミネータを超えない限り、問題は発生しません。変数extra
は、新しい番号を格納するために使用されます。数値がNからN+1桁に変わるときの問題を回避します。この関数sprintf()
は、書き込んだ文字数を返します。これは、データを作業バッファーに安全に追加するために使用されます。
出力例
[] && [ABC] ==> [ABC=1]
[ABC=1] && [ABC] ==> [ABC=2]
[ABC=2] && [ABC] ==> [ABC=3]
[ABC=3] && [XYZ] ==> [ABC=3:XYZ=1]
[ABC=3:XYZ=1] && [XYZ] ==> [ABC=3:XYZ=2]
[ABC=3:XYZ=2] && [XYZ] ==> [ABC=3:XYZ=3]
[ABC=3:XYZ=3] && [DEF] ==> [ABC=3:XYZ=3:DEF=1]
[ABC=3:XYZ=3:DEF=1] && [ABC] ==> [ABC=4:XYZ=3:DEF=1]
[ABC=4:XYZ=3:DEF=1] && [ABC] ==> [ABC=5:XYZ=3:DEF=1]
[ABC=5:XYZ=3:DEF=1] && [ABC] ==> [ABC=6:XYZ=3:DEF=1]
[ABC=6:XYZ=3:DEF=1] && [ABC] ==> [ABC=7:XYZ=3:DEF=1]
[ABC=7:XYZ=3:DEF=1] && [ABC] ==> [ABC=8:XYZ=3:DEF=1]
[ABC=8:XYZ=3:DEF=1] && [ABC] ==> [ABC=9:XYZ=3:DEF=1]
[ABC=9:XYZ=3:DEF=1] && [ABC] ==> [ABC=10:XYZ=3:DEF=1]
[ABC=10:XYZ=3:DEF=1] && [XYZ] ==> [ABC=10:XYZ=4:DEF=1]
[ABC=10:XYZ=4:DEF=1] && [ABC] ==> [ABC=11:XYZ=4:DEF=1]
[ABC=11:XYZ=4:DEF=1] && [GHI] ==> [ABC=11:XYZ=4:DEF=1:GHI=1]
[ABC=11:XYZ=4:DEF=1:GHI=1] && [PQRSTU] ==> [ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1]
[ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1] && [I] ==> [ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1:I=1]
[ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1:I=1] && [I] ==> [ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1:I=2]
[ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1:I=2] && [I] ==> [ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1:I=3]
[ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=1:I=3] && [PQRSTU] ==> [ABC=11:XYZ=4:DEF=1:GHI=1:PQRSTU=2:I=3]
私はいくつかの基本的なチェックを行いvalgrind
(そしていくつかの動的メモリ割り当てを追加し)、それはクリーンな健康法案を思いついた。
診断印刷のスタイルに注意してください。入力と出力が表示されるので便利です。また、文字列を特徴的なマーカー(ここ[]
)で囲んでいるため、迷子のスペースなどを簡単に見つけることができます。