Я создал программу, которая должна читать две 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];
Что вы можете сделать, чтобы помочь решить эту проблему?
Если вы не получаете предупреждений, повышайте настройки компилятора до тех пор, пока он не начнет жаловаться на неинициализированные переменные.
@JonathanLeffler Рассмотрите возможность повторного открытия или редактирования дубликата, чтобы учесть случай многомерного массива, или поиска лучшей цели для дублирования.
Многомерные массивы являются логическим продолжением случая одномерных массивов. Добавлю комментарий, что это относится к многомерным массивам, но это действительно лишнее.
@JonathanLeffler Разница во многом заключается в том, что вы можете использовать функцию изменяемого типа и сохранить синтаксис для доступа к многомерным массивам вместо того, чтобы быть вынужденным использовать одномерный массив и выполнять расчет индекса вручную. Если вы все еще думаете, что это второстепенный вопрос, я позволю сообществу решить.
Как отмечалось в комментариях, вам действительно следует повысить уровень предупреждений в флагах компилятора. Это то, что вы можете получить, если сделаете это. Предупреждающие сообщения ясно указывают на проблему,
<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, занимающий слишком много места, стек взорвется и снова приведет к сбою сегмента. Поскольку мы уже предположили, что ваша реализация поддерживает виртуальные машины, вместо этого вам следует использовать 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;
Использование n
uninitialized приводит к неопределенному значению, а не неопределенному поведению. И неопределенное поведение не означает, что что-то может случиться; это означает, что стандарт C не ограничивает то, что может произойти. Это разные вещи.
@EricPostpischil Использование неопределенного значения в качестве индекса в VLA должно быть UB, верно? Я согласен, что формулировку можно улучшить.
Нет, это не будет неопределенное поведение. Это было бы так, как если бы индекс имел какое-то значение, но стандарт C не определяет, что это за значение.
@EricPostpischil «Каждый раз, когда поток управления проходит через объявление, оценивается выражение (и оно всегда должно иметь значение, большее нуля)». Кажется, предварительное условие нарушено, поэтому это должен быть UB. Цитата взята из cppreference, стандарт я не проверял.
Предварительное условие может быть нарушено, а может и не быть нарушено; это зависит от того, какое значение используется для n
. Таким образом, ситуация может привести к неопределенному поведению, но это не неопределенное поведение как таковое. Это всего лишь неопределенное поведение, когда стандарт C полностью игнорирует то, что происходит. Но это не так; правила, касающиеся неопределенных значений, применяются и управляют поведением программы до тех пор, пока что-то не вызовет неопределенное поведение.
Эти различия могут показаться сложными для новичка, но они влияют на отладку и другую работу с программами.
@EricPostpischil Изменил формулировку. Вы можете напрямую отредактировать ответ, если он по-прежнему вас не удовлетворяет.
@EricPostpischil: Можно утверждать следующее: поскольку по крайней мере одно из допустимых неопределенных вариантов поведения вызывает неопределенное поведение, стандарт C не налагает никаких требований к поведению всей программы.
Ваш
n
не инициализируется, когда вы объявляете массив. Вы не можете объявить VLA таким образом и прочитать размеры позже. Это неопределенное поведение. Кроме того, в зависимости от реализации, использование VLA с большимn
может привести к взрыву стека, даже если вы устраните первую проблему и переместите объявление массива после чтения. Вам следует использоватьmalloc
.