为什么使用 C 引用这个 char 数组会导致堆栈崩溃?

问题描述 投票:0回答:2

该程序采用一个指向

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 警告?导致它的原因是什么?

arrays c function strcat stack-smash
2个回答
4
投票

该函数必须构建包含

"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 );

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
    作为参数:在这种情况下,数组会自动 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;
}
© www.soinside.com 2019 - 2024. All rights reserved.