我创建了一个程序,该程序应该从二进制文件中读取两个 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];
您可以做什么来帮助解决这个问题?
正如评论所指出的,您确实应该在编译器标志中调高警告级别。 这就是你这样做后可以获得的。警告消息清楚地指出了问题,
<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;