Ошибка сегментации многомерных массивов, размер которых определяется во время выполнения

Я создал программу, которая должна читать две 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];

Что вы можете сделать, чтобы помочь решить эту проблему?

Ваш n не инициализируется, когда вы объявляете массив. Вы не можете объявить VLA таким образом и прочитать размеры позже. Это неопределенное поведение. Кроме того, в зависимости от реализации, использование VLA с большим n может привести к взрыву стека, даже если вы устраните первую проблему и переместите объявление массива после чтения. Вам следует использовать malloc.

Weijun Zhou 17.06.2024 01:25

Если вы не получаете предупреждений, повышайте настройки компилятора до тех пор, пока он не начнет жаловаться на неинициализированные переменные.

Stephen Newell 17.06.2024 02:20

@JonathanLeffler Рассмотрите возможность повторного открытия или редактирования дубликата, чтобы учесть случай многомерного массива, или поиска лучшей цели для дублирования.

Weijun Zhou 17.06.2024 07:05

Многомерные массивы являются логическим продолжением случая одномерных массивов. Добавлю комментарий, что это относится к многомерным массивам, но это действительно лишнее.

Jonathan Leffler 17.06.2024 09:32

@JonathanLeffler Разница во многом заключается в том, что вы можете использовать функцию изменяемого типа и сохранить синтаксис для доступа к многомерным массивам вместо того, чтобы быть вынужденным использовать одномерный массив и выполнять расчет индекса вручную. Если вы все еще думаете, что это второстепенный вопрос, я позволю сообществу решить.

Weijun Zhou 17.06.2024 09:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
142
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как отмечалось в комментариях, вам действительно следует повысить уровень предупреждений в флагах компилятора. Это то, что вы можете получить, если сделаете это. Предупреждающие сообщения ясно указывают на проблему,

<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 не ограничивает то, что может произойти. Это разные вещи.

Eric Postpischil 17.06.2024 03:11

@EricPostpischil Использование неопределенного значения в качестве индекса в VLA должно быть UB, верно? Я согласен, что формулировку можно улучшить.

Weijun Zhou 17.06.2024 03:13

Нет, это не будет неопределенное поведение. Это было бы так, как если бы индекс имел какое-то значение, но стандарт C не определяет, что это за значение.

Eric Postpischil 17.06.2024 03:14

@EricPostpischil «Каждый раз, когда поток управления проходит через объявление, оценивается выражение (и оно всегда должно иметь значение, большее нуля)». Кажется, предварительное условие нарушено, поэтому это должен быть UB. Цитата взята из cppreference, стандарт я не проверял.

Weijun Zhou 17.06.2024 03:15

Предварительное условие может быть нарушено, а может и не быть нарушено; это зависит от того, какое значение используется для n. Таким образом, ситуация может привести к неопределенному поведению, но это не неопределенное поведение как таковое. Это всего лишь неопределенное поведение, когда стандарт C полностью игнорирует то, что происходит. Но это не так; правила, касающиеся неопределенных значений, применяются и управляют поведением программы до тех пор, пока что-то не вызовет неопределенное поведение.

Eric Postpischil 17.06.2024 03:18

Эти различия могут показаться сложными для новичка, но они влияют на отладку и другую работу с программами.

Eric Postpischil 17.06.2024 03:19

@EricPostpischil Изменил формулировку. Вы можете напрямую отредактировать ответ, если он по-прежнему вас не удовлетворяет.

Weijun Zhou 17.06.2024 03:21

@EricPostpischil: Можно утверждать следующее: поскольку по крайней мере одно из допустимых неопределенных вариантов поведения вызывает неопределенное поведение, стандарт C не налагает никаких требований к поведению всей программы.

Andreas Wenzel 12.07.2024 17:00

Другие вопросы по теме