大小在运行时确定的数组的分段错误

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

我创建了一个程序,该程序应该从二进制文件中读取两个 3D 矩阵,将它们相乘,然后将结果打印到二进制文件中。然而,虽然它成功编译,但当我运行它时,它给了我一个分段错误错误。

这是我用于该程序的代码:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    int n2;

    int matrix1[n][n][n], matrix2[n][n][n], result_matrix[n][n][n];

    FILE *file1, *file2, *result;

    file1 = fopen("matrix1.bin", "rb");
    file2 = fopen("matrix2.bin", "rb");

    fread(&n, sizeof(int), 1, file1);
    fread(&n2, sizeof(int), 1, file2);

    if (n != n2 || n > 100) {
        printf("Error: Incompatible matrices or n greater than 100.");
        return 1;
    }

    fread(matrix1, sizeof(int), n * n * n, file1);
    fread(matrix2, sizeof(int), n * n * n, file2);

    fclose(file1);
    fclose(file2);

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                result_matrix[i][j][k] = 0;

                for (int l = 0; l < n; l++) {
                    result_matrix[i][j][k] += matrix1[i][j][l] * matrix2[l][j][k];
                }
            }
        }
    }

    result = fopen("result.bin", "wb");

    fwrite(&n, sizeof(int), 1, result);
        
    fwrite(result_matrix, sizeof(int), n * n * n, result);

    fclose(result);

    return 0;
}

这就是发生分段错误的具体位置:

Program received signal SIGSEGV, Segmentation fault.
0x0000555555555c3a in main () at 31504085_1.c:35
35                    result_matrix[i][j][k] += matrix1[i][j][l] * matrix2[l][j][k];

您可以做什么来帮助解决这个问题?

c segmentation-fault
1个回答
3
投票

正如评论所指出的,您确实应该在编译器标志中调高警告级别。 就是你这样做后可以获得的。警告消息清楚地指出了问题,

<source>: In function 'main':
<source>:8:5: warning: 'n' is used uninitialized [-Wuninitialized]
    8 |     int matrix1[n][n][n], matrix2[n][n][n], result_matrix[n][n][n];
      |     ^~~
<source>:5:9: note: 'n' declared here
    5 |     int n;
      |         ^

使用未初始化的变量会产生不确定的值,这本身并不是未定义的行为,但使用不确定的值声明数组(具体来说,VLA,稍后会详细介绍该主题)可能会导致未定义的行为,因为前提条件是大小表达式必须计算为大于零的值,否则可能会被违反。当它确实导致未定义的行为时,任何事情都可能发生,而段错误是许多可能的结果之一。无论是否存在未定义的行为,使用不确定的值来声明数组在语义上都是错误的,并且绝对是您想要实现的目标。

因此,第一次修复尝试是:

    int n;

    /* ... */

    fread(&n, sizeof(int), 1, file1);
    
    /* ... */

    int matrix1[n][n][n], matrix2[n][n][n], result_matrix[n][n][n];

当您声明一个像

matrix1[n][n][n]
这样的数组时,其中维度的范围不是整数常量表达式,您正在使用C语言中的一个功能,称为可变长度数组(简称VLA)。此功能不需要符合标准的 C 编译器支持,并且根据定义,您的程序不太可移植。考虑到这一点并假设您确定您的编译器确实支持 VLA,并且自 C23 以来,这也意味着对可变修改类型 (VM) 的支持,让我们进一步检查代码的其他问题。

假设您的实现确实使用了堆栈,则由实现决定是否将 VLA 放入堆栈中。在典型的实现中,堆栈大小是有限的,如果您尝试使用占用过多空间的 VLA,堆栈将崩溃并再次导致段错误。由于我们已经假设您的实现支持 VM,因此您应该使用

malloc
为空闲存储中的 VLA 分配内存。通过利用对 VM 的支持,您可以在不失去多维数组访问语法的便利性的情况下做到这一点。为了让事情变得更容易,我们可以使用
typedef
。我还修改了代码来检查
fread
的返回值,以防止出现空文件。您还应该始终检查程序中其他标准库函数的返回值(如果可用)。

    int n;

    /* ... */

    if(fread(&n, sizeof(int), 1, file1) != 1){
        fputs("Failed to read matrix dimension.\n", stderr);
        return EXIT_FAILURE;
    }
    
    /* ... */
    typedef int (*matrix_t)[n][n][n]; //A variably-modified type
    matrix_t matrix1 = malloc(sizeof *matrix1);
    if(!matrix1){
         fputs("Malloc failed: matrix1.\n", stderr);
         return EXIT_FAILURE;
    }
    matrix_t matrix2 = malloc(sizeof *matrix2);
    /* ... */

然后,在循环内,您可以取消引用指针来执行实际计算,

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                (*result_matrix)[i][j][k] = 0;

                for (int l = 0; l < n; l++) {
                    (*result_matrix)[i][j][k] += (*matrix1)[i][j][l] * (*matrix2)[l][j][k];
                }
            }
        }
    }

在函数结束时,你应该记住

free
分配的内存,

    free(matrix1);
    free(matrix2);
    free(result_matrix);
    return EXIT_SUCCESS;
© www.soinside.com 2019 - 2024. All rights reserved.