英文:
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 forstrtok
failure 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 typeint
in 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 usesnprintf
instead.strcat(arr, one);
instead of relying onarr
having 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 passstr
as 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;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论