英文:
Why does referencing this char array cause Stack smashing, using C?
问题
程序接受一个指向char数组和一个int的指针。char数组由两个数字组成,用空格分隔。
该函数的用途是将char数组的值读取为整数,并用输入的乘积值替换它们:
void read_and_mul(char *arr, int scale) {
int num_arr[2]; // 保存值在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)); // 删除char[]的先前值
char one[sizeof(int)];
char two[sizeof(int)];
sprintf(one, "%d", num_arr[0]); // 将更改后的数字保存为字符
sprintf(two, "%d", num_arr[1]);
strcat(arr, one); // 将乘以的值写入字符串
strcat(arr, " ");
strcat(arr, two);
}
然而,如果我像这样使用它,它按预期工作但导致了栈溢出:
int main(int argc, char *argv[]) {
char str[] = "1 2";
read_and_mul((char *)&str, 10);
printf("调用后的字符串:%s\n", str);
return 0;
}
CLion终端中的消息是:
***检测到栈溢出***:终止
调用后的字符串:10 20
这是潜在的错误,不是IDE警告,原因是什么?
英文:
The program takes a pointer to a char array and an int. The char array consists of two numbers, separated by a space.
The use of the function is to read the values of the char array as integers and replace them with the multiplied value of the input:
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);
}
However if I use it like this, it works as intended but causes a stack-smashing:
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;
}
The terminal message in CLion is:
*** stack smashing detected ***: terminated
string after call: 10 20
Is this a potential error or an IDE warning and what is causing it?
答案1
得分: 4
以下是翻译好的部分:
函数必须构建包含6个字符(包括终止的空字符'\0')的字符串"10 20"。
但您试图将此字符串存储在只有4个字符的数组中
char str[] = "1 2";
由于以下语句
strcat(arr,one); // 将乘积值写入字符串
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, "%lld", ( long long int )value * scale );
pos += sprintf( pos, "%lld", ( long long int )value * scale );
英文:
The function has to build the string "10 20" that contains 6 characters including the terminating null character '\0'.
But you are trying to store this string in an array that has only 4 characters
char str[] = "1 2";
due to these statements
strcat(arr,one); // writing the multiplied values to the string
strcat(arr, " ");
strcat(arr,two);
As a result the function already invokes undefined behavior.
Another problem is in this call of memset:
memset(arr,0,sizeof(arr));
The variable arr within the function has the pointer type char *. If sizeof( char * ) is equal to 8 then again there is an attempt to write to memory outside the array.
And the function should not depend on magic numbers like 2 used in this declaration
int num_arr[2];
You should always try to write more general functions.
To resolve the problem you should within the function allocate dynamically a new character array where the result string will be stored and return a pointer to the array from the function.
Also, pay attention to that it will be more clear and correct to write
read_and_mul( str, 10 );
instead of
read_and_mul((char *) &str, 10);
Here is a demonstration program that shows a possible approach to solve the task.
#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 );
}
The program output is
"10 20 30 40 50 60 70 80 90 100"
As in general multiplication of two integers can result in overflow then to avoid such a situation you may change these statements
length += snprintf( NULL, 0, "%d", value * scale );
pos += sprintf( pos, "%d", value * scale );
to the following
length += snprintf( NULL, 0, "%lld", ( long long int )value * scale );
pos += sprintf( pos, "%lld", ( long long int )value * scale );
答案2
得分: 0
以下是要翻译的内容:
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作为参数传递:在这种情况下,数组会自动退化为指向其第一个元素的指针。
以下是修改后的版本:
#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) {
// 字符串包含2个整数,
// 尝试用缩放后的值覆盖它们
size_t len = snprintf(s, size, "%d %d", a * scale, b * scale);
if (len >= size)
return -2; // 溢出
else
return 0;
}
return -1; // 解析错误:字符串不包含至少2个整数
}
int main(int argc, char *argv[]) {
char str[100] = "1 2";
int status = read_and_mul(str, sizeof str, 10);
printf("调用后的字符串:%s,状态 = %d\n", str, status);
return 0;
}
英文:
There are multiple problems in the code:
char *ch = strtok(arr, " ");you do not check forstrtokfailure to find a word.strtok()would return a null pointer in this case andatoi(ch)would cause undefined behavior, probably a segmentation fault.- same problem for the second word
memset(arr, 0, sizeof(arr));this is useless andsizeof(arr)is the size of a pointer, not the length of the array pointed to byarr. Another instance of undefined behavior if the array passed to the function is too small.char one[sizeof(int)];the size of typeintin bytes is not the length of the representation of an integer in decimal digits. It is always too small. You do not need these intermediary arrays: you could compose the final string directly into the array pointed to bystr.sprintf(one, "%d", num_arr[0]);you cannot pass the size of the destination array tosprintf: you would have undefined behavior if the representation as a string does not fit in the target array. You should usesnprintfinstead.strcat(arr, one);instead of relying onarrhaving been erased, you could usestrcpy. But in all cases you assume that the target array is large enough for the constructed string: this is risky. It would be better for the function to take the array length as an extra argument and to protect against buffer overflow withsnprintf().char str[] = "1 2";The array is just long enough for"1 2", it will be too short for"10 20". This is the stack smashing detected by your IDE.read_and_mul((char *)&str, 10);instead of casting(char *)&str, you should just passstras an argument: an array automatically decays into a pointer to its first element in this case.
Here is a modified version:
#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;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论