Проблемы с выходом за рамки двумерного динамического массива

Вот моя инициализация динамического двумерного массива 3 на 3 :

double** A = (double**)calloc(3, sizeof(double*));
for(int i = 0; i < 3; ++i){
    A[i] = (double*)calloc(3, sizeof(double));
}

Когда я вывожу содержимое динамического двумерного массива в терминал с размерами, обратите внимание, 4 на 3, то мне выдаётся Segmentation fault, это понятно.

Но(!) когда я вывожу содержимое динамического двумерного массива в терминал с размерами, вновь обратите внимание, 3 на 4, да хоть 3 на 20, то Segmentation fault не выдаётся, вместо этого в терминале следующее :

0.000000 0.000000 0.000000 0.000000 
0.000000 0.000000 0.000000 0.000000 
0.000000 0.000000 0.000000 0.000000

С чем это связано, ведь я, так или иначе, выхожу за рамки массива? Почему мне не выдаёт в терминале Segmentation fault?


Ответы (3 шт):

Автор решения: Aleksandr

Ваш код вызывает неопределенное поведение (undefined behavior) при попытке обращения к элементам за пределами выделенного динамического массива. Более конкретно, сегментационная ошибка (segmentation fault) может возникать в случае обращения за пределы памяти, которая выделена для массива.

Почему Segmentation fault не возникает, когда вы выводите содержимое динамического двумерного массива с размерами, большими, чем выделенная память? Это связано с тем, что точное поведение в случае обращения к недопустимой памяти является неопределенным. Компилятор и операционная система могут справляться с этими ситуациями по-разному. Ваш код может продолжать работать и выводить значения, тем самым создавая иллюзию правильной работы, но это неправильное и небезопасное поведение.

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

→ Ссылка
Автор решения: Aleksandr

Ваш код для инициализации динамического двумерного массива 3x3 выглядит правильно.

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

int main() {
    double** A = (double**)calloc(3, sizeof(double*));
    for(int i = 0; i < 3; ++i){
        A[i] = (double*)calloc(3, sizeof(double));
    }

    // Используйте массив A по вашему усмотрению

    // Освободите память после использования массива
    for(int i = 0; i < 3; ++i){
        free(A[i]);
    }
    free(A);

    return 0;
}

После использования массива необходимо освободить память путем вызова free для каждого из выделенных блоков и, наконец, вызова free для самого массива A.

→ Ссылка
Автор решения: AlexGlebe

В текущей ситуации обращение к памяти контролируется системой с помощью таблицы прав доступа. И если программа пытается модифицировать память чужой программы, то её система сразу завершает без прав отступления. То-есть получает системную ошибку Segmentation fault.
Но данная таблица должна иметь место в памяти и если на каждый байт задавать отдельные права процессам, то памяти будет очень сильно не хватать. Операционные и вычислительные системы работают со страницами памяти.
Система Linux например выделяет 4096 байт минимум и записывает для выделенной страницы права только для текущего процесса.
Пользоваться памятью текущей страницы вне запрошенного размера всё равно нельзя. Так как компилятор может использовать её по своему усмотрению. На пример храня технические данные выделенной памяти.

Например выделили память :

A[1] = (double*)calloc(3, sizeof(double));

Указатель A[1] будет иметь адрес выделенной памяти и доступ A[1][100] будет разрешён, так как адреса A[1][0] и A[1][100] близки и находятся на одной странице памяти.
А если вы хотите изменить A[100][1] то может быть ошибка, так как вы указатель никакой не задавали для A[100] = ? и там он может быть совершенно случайным.

→ Ссылка