该程序采用一个指向
char
数组和 int
的指针。 char
数组由两个数字组成,以空格分隔。
该函数的用途是将
char
数组的值读取为整数,并将其替换为输入的相乘值:
void read_and_mul(char *arr, int scale) {
int num_arr[2]; // saving values in a int[]
char *ch = strtok(arr, " ");
num_arr[0] = scale * (atoi(ch));
ch = strtok(NULL, " ");
num_arr[1] = scale * (atoi(ch));
memset(arr, 0, sizeof(arr)); // deleting the previous value of the char[]
char one[sizeof(int)];
char two[sizeof(int)];
sprintf(one, "%d", num_arr[0]); // saving the altered numbers as chars
sprintf(two, "%d", num_arr[1]);
strcat(arr, one); // writing the multiplied values to the string
strcat(arr, " ");
strcat(arr, two);
}
但是,如果我像这样使用它,它会按预期工作,但会导致堆栈崩溃:
int main(int argc, char *argv[]) {
char str[] = "1 2";
read_and_mul((char *)&str, 10);
printf("string after call: %s\n", str);
return 0;
}
CLion 中的终端消息是:
*** stack smashing detected ***: terminated
string after call: 10 20
这是一个潜在的错误还是 IDE 警告?导致它的原因是什么?
该函数必须构建包含
"10 20"
个字符(包括终止空字符 6
)的字符串 '\0'
。
但是您试图将此字符串存储在仅包含
4
个字符的数组中
char str[] = "1 2";
由于这些陈述
strcat(arr,one); // writing the multiplied values to the string
strcat(arr, " ");
strcat(arr,two);
因此,该函数已经调用了未定义的行为。
另一个问题是在
memset
的这个调用中:
memset(arr,0,sizeof(arr));
函数内的变量
arr
具有指针类型char *
。如果 sizeof( char * )
等于 8
,则再次尝试写入数组外部的内存。
并且该函数不应该依赖于像本声明中使用的
2
这样的幻数
int num_arr[2];
您应该始终尝试编写更通用的函数。
要解决此问题,您应该在函数内动态分配一个新的字符数组,其中将存储结果字符串,并从函数返回指向该数组的指针。
还有注意这样写起来会更清晰正确
read_and_mul( str, 10 );
而不是
read_and_mul((char *) &str, 10);
这是一个演示程序,展示了解决该任务的可能方法。
#include <stdio.h>
#include <stdlib.h>
char * read_and_mul( const char *s, int scale )
{
size_t n = 0;
size_t length = 0;
const char *tmp = s;
int value;
for (char *endptr; value = strtol( tmp, &endptr, 10 ), endptr != tmp; tmp = endptr)
{
++n;
length += snprintf( NULL, 0, "%d", value * scale );
}
length += n == 0 ? 1 : n;
char *result = calloc( length, sizeof( char ) );
if (result != NULL)
{
const char *tmp = s;
int first = 1;
for (char *pos = result, *endptr; value = strtol( tmp, &endptr, 10 ), endptr != tmp; tmp = endptr)
{
if (!first)
{
*pos++ = ' ';
}
else
{
first = 0;
}
pos += sprintf( pos, "%d", value * scale );
}
}
return result;
}
int main( void )
{
char s[] = "1 2 3 4 5 6 7 8 9 10";
char *result = read_and_mul( s, 10 );
if (result) printf( "\"%s\"\n", result);
free( result );
}
程序输出为
"10 20 30 40 50 60 70 80 90 100"
一般来说,两个整数相乘可能会导致溢出,那么为了避免这种情况,您可以更改这些语句
length += snprintf( NULL, 0, "%d", value * scale );
pos += sprintf( pos, "%d", value * scale );
以下
length += snprintf( NULL, 0, "%lld", ( long long int )value * scale );
pos += sprintf( pos, "%lld", ( long long int )value * scale );
代码中存在多个问题:
char *ch = strtok(arr, " ");
您没有检查strtok
是否找不到单词。在这种情况下,strtok()
将返回空指针,并且atoi(ch)
将导致未定义的行为,可能是分段错误。memset(arr, 0, sizeof(arr));
这是没有用的,sizeof(arr)
是指针的大小,而不是arr
指向的数组的长度。如果传递给函数的数组太小,则会出现未定义行为的另一个实例。char one[sizeof(int)];
类型 int
的大小(以字节为单位)不是整数以十进制数字表示的长度。它总是太小。您不需要这些中间数组:您可以将最终字符串直接组成到 str
指向的数组中。sprintf(one, "%d", num_arr[0]);
您无法将目标数组的大小传递给 sprintf
:如果字符串表示形式不适合目标数组,则会出现未定义的行为。您应该使用 snprintf
来代替。strcat(arr, one);
,您可以使用arr
,而不是依赖于strcpy
已被擦除。但在所有情况下,您都假设目标数组对于构造的字符串来说足够大:这是有风险的。该函数最好将数组长度作为额外参数,并使用
snprintf()
防止缓冲区溢出。char str[] = "1 2";
该数组对于"1 2"
来说足够长,对于"10 20"
来说就太短了。这是您的 IDE 检测到的堆栈粉碎。read_and_mul((char *)&str, 10);
而不是强制转换 (char *)&str
,您应该只传递 str
作为参数:在这种情况下,数组会自动 decays 变成指向其第一个元素的指针。这是修改后的版本:
#include <stdio.h>
int read_and_mul(char *s, size_t size, int scale) {
int a, b;
if (sscanf(s, "%d%d", &a, &b) == 2) {
// the string contains 2 integers,
// attempt to overwrite with the scaled values
size_t len = snprintf(s, size, "%d %d", a * scale, b * scale);
if (len >= size)
return -2; // overflow
else
return 0;
}
return -1; // parsing error: the string does not contain at least 2 integers
}
int main(int argc, char *argv[]) {
char str[100] = "1 2";
int status = read_and_mul(str, sizeof str, 10);
printf("string after call: %s, status = %d\n", str, status);
return 0;
}