我有一个大型的遗留程序,其中以编程方式在程序初始化中以编程方式更改argv参数,然后是解析参数的逻辑。
在发布模式下,程序正常终止。
在调试模式下,程序执行所有必需的计算并提供正确的输出。但退出时会出现Heap损坏错误:
错误信息:
Microsoft Visual C ++运行时库
调试错误!
程序:... sers \ AMD \ source \ repos \ ArgvOverflow \ x64 \ Debug \ ArgvOverflow.exe
HEAP CORRUPTION DETECTED:在CRT块(#62)之后的0x00000259566FDC90。 CRT检测到应用程序在堆缓冲区结束后写入内存。
内存分配在minkernel \ crts \ ucrt \ src \ appcrt \ startup \ argv_parsing.cpp(285)。
(按“重试”调试应用程序)
中止重试忽略
码:
#include <stdio.h>
int main(int argc, char **argv)
{
argc = 12;
argv[1] = "str1";
argv[2] = "str2";
argv[3] = "str3";
argv[4] = "str4";
argv[5] = "str5";
argv[6] = "str6";
argv[7] = "str7";
argv[8] = "str8";
argv[9] = "str9";
argv[10] = "str10";
argv[11] = "str11";
printf("Hello world\n");
return 0;
}
我已经阅读了几篇关于修改argv的帖子,他们声称这些修改根据C标准是合法的。我也尝试了这条线的建议
argv[argc] = NULL;
这不是解决问题的方法。
修改参数字符串的字符是合法的。 C11 5.1.2.2.1p2
参数
argc
和argv
以及argv
数组指向的字符串应该可由程序修改,并在程序启动和程序终止之间保留它们最后存储的值。
它仍然不允许访问数组越界,argv
将只有argc + 1
元素,而不是你尝试在那里的东西。
你不能像这样乱用argv
。 argv
不是你的,它在程序启动期间根据命令行分配和填充。
如果你这样调用你的程序:
program a b c
然后argc
是4,argv[0]
指向"program"
,argv[1]
指向"a"
等。
argv[argc]
是NULL
但访问argv[5]
及更高版本是未定义的行为,因为您访问数组越界。
这篇SO文章也可能有所帮助:How dangerous is it to access an array out of bounds?。
解决您的问题:不要访问超出界限的数组。
您可以修改argc
和argv
,但这并不意味着C突然为您处理(重新)这些变量的分配。 argv
将是char* argv[argc];
类型的阵列。它将包含与argc
一样多的指针,表示它包含,不多也不少。类似地,argv[i]
指向的每个字符串的长度与调用者通过的长度一样长。
例:
myprog.exe foo
argc == 2
和argv
的长度为2.这个程序无法更改。argv[0]
将指向一个可修改的字符串"myprog.exe"
,大小10 + 1 = 11个字节。您可以更改内容,但不能存储超过11个字节的内容。argv[1]
将指向字符串"foo"
,大小3 + 1 = 4字节。您可以更改内容,但不能存储超过4个字节的内容。(好奇心:它是完全正确的,可以说是将qazxswpoi定义为VLA的最正确方法,如下所示:
argv
,因为无论如何int main (int argc, char* argv[argc])
衰变成argv
。)
尽管如此,修改char**
和argc
虽然是C标准所允许的,但却是非常糟糕的做法。不要这样做。相反,你应该使用一个局部变量,让它在需要的地方引用argv
。例:
argv
为了解决这个问题,我创建了一个单独的char **变量,并在代码中使用该变量来解决问题。
以下是新代码的样子:
int main (int argc, char* argv[])
{
const char* argstr [12] =
{
"str0",
"str1",
"str2",
"str3",
"str4",
"str5",
"str6",
"str7",
"str8",
"str9",
"str10",
"str11",
};
for(int i=0; i<argc; i++)
{
argstr[i] = argv[i];
}
/* always use argstr here, never argv */
return 0;
}
在进行调试时进行这种设置是完全合理的。为我工作的一个工作是在调用程序时设置一个伪参数列表,然后在进入main后用所需数据重新填充argv。
#include <stdio.h>
int main(int argc, char **argv)
{
int nargc = 12;
char **nargv;
nargv = malloc(sizeof(char*)*nargc);
nargv[0] = malloc(1 + strlen(argv[0]));
strcpy(nargv[0], argv[0]);
nargv[1] = malloc(1 + strlen("srt1"));
strcpy(nargv[1], "srt1");
nargv[2] = malloc(1 + strlen("srt2"));
strcpy(nargv[2], "srt2");
nargv[3] = malloc(1 + strlen("srt3"));
strcpy(nargv[3], "srt3");
nargv[4] = malloc(1 + strlen("srt4"));
strcpy(nargv[4], "srt4");
nargv[5] = malloc(1 + strlen("srt5"));
strcpy(nargv[5], "srt5");
nargv[6] = malloc(1 + strlen("srt6"));
strcpy(nargv[6], "srt6");
nargv[7] = malloc(1 + strlen("srt7"));
strcpy(nargv[7], "srt7");
nargv[8] = malloc(1 + strlen("srt8"));
strcpy(nargv[8], "srt8");
nargv[9] = malloc(1 + strlen("srt9"));
strcpy(nargv[9], "srt9");
nargv[10] = malloc(1 + strlen("srt10"));
strcpy(nargv[10], "srt10");
nargv[11] = malloc(1 + strlen("srt11"));
strcpy(nargv[11], "srt11");
/* Useful code */
free(nargv[11]);
free(nargv[10]);
free(nargv[9]);
free(nargv[8]);
free(nargv[7]);
free(nargv[6]);
free(nargv[5]);
free(nargv[4]);
free(nargv[3]);
free(nargv[2]);
free(nargv[1]);
free(nargv[0]);
free(nargv);
printf("Hello world\n");
return 0;
}
在你的代码里面
myprog.exe dummy dummy dummy dummy dummy dummy dummy dummydummy dummydummy dummy