開発環境:AMDプロセッサ、Windows 10 64bit、Cygwin64、Visual Studio Code、Code Runner拡張機能
問題が発生したコード例:
#include<string.h>
#include<stdio.h>
typedef char DataType;
typedef struct TreeNode
{
DataType value;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode, *TreePtr;
TreeNode* parseExpression(const char *input)
{
const char *ptr = input;
int start = strchr(ptr, '(') - ptr + 1;
int endPos;
ptr = ptr + strlen(ptr) - 1;
for (int i = strlen(ptr) - 1;; i--, ptr--)
{
if(*ptr == ')'){
endPos = i;
printf("終了位置=%d, インデックス=%d\n", endPos, i);
break;
}
}
ptr = input + start;
char buffer[256] = "";
char *dest = buffer;
for (int i = 0; i < endPos - start + 1; i++, ptr++, dest++){
*dest = *ptr;
printf("ループ=%d, 終了位置=%d, 開始位置=%d\n", i, endPos, start);
}
*dest = '\0';
dest = buffer;
printf("\n抽出結果:%s\n", dest);
return NULL;
}
int main()
{
TreePtr root = NULL;
const char* expression = "A(B(D,E(H(J,E(L,M(,N)))),C(F,G(,I)))";
root = parseExpression(expression);
return 0;
}
View Code実行結果の問題点:
修正後のコード例:
#include<string.h>
#include<stdio.h>
typedef char DataType;
typedef struct TreeNode
{
DataType value;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode, *TreePtr;
int main()
{
TreePtr root = NULL;
char expression[] = "A(B(D,E(H(J,E(L,M(,N)))),C(F,G(,I)))";
const char *source = expression;
const char *searchPtr;
int startPos = strchr(source, '(') - source + 1;
int endPos;
searchPtr = source + strlen(source) - 1;
for (int index = strlen(source) - 1;; index--, searchPtr--)
{
if(*searchPtr == ')'){
endPos = index;
printf("終了位置=%d, インデックス=%d\n", endPos, index);
break;
}
}
searchPtr = source + startPos;
char extracted[256] = "";
char *target = extracted;
for (int loop = 0; loop < endPos - startPos + 1; loop++, searchPtr++, target++){
*target = *searchPtr;
printf("ループ=%d, 終了位置=%d, 開始位置=%d\n", loop, endPos, startPos);
}
*target = '\0';
target = extracted;
printf("\n抽出結果:%s\n", target);
return 0;
}
View Code問題解決のアプローチ:
文字列リテラルと配列のメモリ管理の違いが原因であった。関数に渡される文字列リテラルは読み取り専用メモリ領域に配置されるため、変更を試みると未定義の動作が発生する。一方、main関数内で宣言した配列はスタックに確保され、安全に変更が可能である。このメモリ配置の違いが変数の予期せぬ挙動を引き起こしていた。
また、バッファサイズを明示的に指定することで、バッファオーバーフローのリスクを軽減し、より堅牢なコードとなった。関数内での処理をmain関数に移行することで、変数のスコープとライフタイムをより明確に管理できるようになった。