为什么引用这个字符数组会导致堆栈溢出,使用C?

huangapple go评论108阅读模式
英文:

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

以下是要翻译的内容:

  1. char *ch = strtok(arr, " "); 你没有检查 strtok 是否找到单词。在这种情况下,strtok() 会返回空指针,atoi(ch) 会导致未定义行为,可能是段错误。
  2. 第二个单词存在相同的问题。
  3. memset(arr, 0, sizeof(arr)); 这是无用的,sizeof(arr) 是指针的大小,而不是 arr 指向的数组的长度。如果传递给函数的数组太小,会再次导致未定义行为。
  4. char one[sizeof(int)]; 类型 int 的大小以字节为单位并不等于整数的十进制表示的长度。它总是太小了。你不需要这些中间数组:你可以直接将最终字符串组合到由 str 指向的数组中。
  5. sprintf(one, "%d", num_arr[0]); 你不能将目标数组的大小传递给 sprintf:如果字符串表示形式不适合目标数组,会导致未定义行为。你应该使用 snprintf
  6. strcat(arr, one); 与其依赖于 arr 已被“删除”,不如使用 strcpy。但在所有情况下,你都假设目标数组足够大以容纳构建的字符串:这是有风险的。最好的方法是使函数接受数组长度作为额外参数,并使用 snprintf() 防止缓冲区溢出。
  7. char str[] = "1 2"; 这个数组只足够长以容纳 "1 2",对于 "10 20" 来说太短了。这是你的IDE检测到的栈溢出
  8. 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, &quot; &quot;); you do not check for strtok failure to find a word. strtok() would return a null pointer in this case and atoi(ch) would cause undefined behavior, probably a segmentation fault.
  • same problem for the second word
  • memset(arr, 0, sizeof(arr)); this is useless and sizeof(arr) is the size of a pointer, not the length of the array pointed to by arr. Another instance of undefined behavior if the array passed to the function is too small.
  • char one[sizeof(int)]; the size of type int 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 by str.
  • sprintf(one, &quot;%d&quot;, num_arr[0]); you cannot pass the size of the destination array to sprintf: you would have undefined behavior if the representation as a string does not fit in the target array. You should use snprintf instead.
  • strcat(arr, one); instead of relying on arr having been erased, you could use strcpy. 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 with snprintf().
  • char str[] = &quot;1 2&quot;; The array is just long enough for &quot;1 2&quot;, it will be too short for &quot;10 20&quot;. This is the stack smashing detected by your IDE.
  • read_and_mul((char *)&amp;str, 10); instead of casting (char *)&amp;str, you should just pass str as an argument: an array automatically decays into a pointer to its first element in this case.

Here is a modified version:

#include &lt;stdio.h&gt;

int read_and_mul(char *s, size_t size, int scale) {
    int a, b;
    if (sscanf(s, &quot;%d%d&quot;, &amp;a, &amp;b) == 2) {
        // the string contains 2 integers,
        // attempt to overwrite with the scaled values
        size_t len = snprintf(s, size, &quot;%d %d&quot;, a * scale, b * scale);
        if (len &gt;= 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] = &quot;1 2&quot;;
    int status = read_and_mul(str, sizeof str, 10);
    printf(&quot;string after call: %s, status = %d\n&quot;, str, status);
    return 0;
}

huangapple
  • 本文由 发表于 2023年8月10日 23:48:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76877372.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定