Проблема с динамическим выделением памяти во вспомогательной функции, в заголовочном файле

Те, кто знаком с проблемой фильтра CS50, могут пропустить первый абзац.

При попытке написать код для blur функции CS50 Filter Problem, в которой мне нужно реализовать только фильтр, остальная часть написана заранее персоналом CS50 (поэтому не просите меня использовать это вместо то, я ничего не могу изменить кроме кода функции.).

Чтобы сделать размытое изображение, я попытался скопировать RGBTRIPLE(*image)[width] = calloc(height, width * sizeof(RGBTRIPLE));. Сначала статически, в котором мне пришлось запустить вложенный цикл, чтобы сделать копию исходного изображения. И это сработало.

Но затем, когда я попытался использовать calloc и вместо этого просто скопировать указатель, image = temp; результаты были другими. и это единственное, что я изменил.

Если куча памяти не очищается как стек, что могло пойти не так?

ПРИМЕЧАНИЕ. Все эти функции хранятся в разных файлах, а не в одном и том же.

Это имеет значение?


Статический:

void blur(int height, int width, RGBTRIPLE image[height][width])
    {
            RGBTRIPLE temp[height][width];
            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    temp[i][j] = image[i][j];
                }
            }
            int i, j, k, l, prev_row, prev_col, next_col, next_row, c;
            float sumRed, sumGreen, sumBlue;
            
            for (i = 0; i < height; i++)
            {
                prev_row = i == 0 ? i : i - 1;
                next_row = i == height - 1 ? i : i + 1;
        
                for (j = 0; j < width; j++)
                {
                    prev_col = j == 0 ? j : j - 1;
                    next_col = j == width - 1 ? j : j + 1;
                    
                    sumRed = 0;
                    sumGreen = 0;
                    sumBlue = 0;
                    c = 0;
                    
                    for (k = prev_row; k <= next_row; k++)
                    {
                        for (l = prev_col; l <= next_col; l++)
                        {
                            sumRed += temp[k][l].rgbtRed;
                            sumGreen += temp[k][l].rgbtGreen;
                            sumBlue += temp[k][l].rgbtBlue;
                            c++;
                        }
                    }
                    image[i][j].rgbtRed = round(sumRed / (float) c);
                    image[i][j].rgbtGreen = round(sumGreen / (float) c);
                    image[i][j].rgbtBlue = round(sumBlue / (float) c);
                }
            }
            return;
        }

ВЫХОД:

:) blur correctly filters middle pixel
:) blur correctly filters pixel on edge
:) blur correctly filters pixel in corner
:) blur correctly filters 3x3 image
:) blur correctly filters 4x4 image

Динамический:

RGBTRIPLE(*temp)[width] = calloc(height, width * sizeof(RGBTRIPLE));
int i, j, k, l, prev_row, prev_col, next_col, next_row, c;
float sumRed, sumGreen, sumBlue;

for (i = 0; i < height; i++)
{
    prev_row = i == 0 ? i : i - 1;
    next_row = i == height - 1 ? i : i + 1;

    for (j = 0; j < width; j++)
    {
        prev_col = j == 0 ? j : j - 1;
        next_col = j == width - 1 ? j : j + 1;
        
        sumRed = 0;
        sumGreen = 0;
        sumBlue = 0;
        c = 0;
        
        for (k = prev_row; k <= next_row; k++)
        {
            for (l = prev_col; l <= next_col; l++)
            {
                sumRed += image[k][l].rgbtRed;
                sumGreen += image[k][l].rgbtGreen;
                sumBlue += image[k][l].rgbtBlue;
                c++;
            }
        }
        temp[i][j].rgbtRed = round(sumRed / (float) c);
        temp[i][j].rgbtGreen = round(sumGreen / (float) c);
        temp[i][j].rgbtBlue = round(sumBlue / (float) c);
    }
}
image = temp;
return;

Выход:

:( blur correctly filters middle pixel
    expected "127 140 149\n", not "120 140 150\n"
:( blur correctly filters pixel on edge
    expected "80 95 105\n", not "40 50 60\n"
:( blur correctly filters pixel in corner
    expected "70 85 95\n", not "10 20 30\n"
:( blur correctly filters 3x3 image
    expected "70 85 95\n80 9...", not "10 20 30\n40 5..."
:( blur correctly filters 4x4 image
    expected "70 85 95\n80 9...", not "10 20 30\n40 5..."

НЕТ!!! Память стека не очищается; память кучи, выделенная с помощью calloc, очищается. Таким образом, ошибка заключается в вашей первой попытке с переменными, выделенными в стеке.

Paul Ogilvie 21.12.2020 12:03

@PaulOgilvie, это другое ... Память стека не очищается после завершения вызова функции?

Sarthak 21.12.2020 12:06

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

Paul Ogilvie 21.12.2020 12:07
image = temp; Что такое image? Он передается в функцию? Не могли бы вы показать это? Если передается как параметр функции, то это локальная переменная, и ее изменение не меняет значение вызывающего объекта.
kaylum 21.12.2020 12:09

Я использовал этот двухмерный массив структур, инициализировал его, чтобы создать копию исходного массива. В то время как с calloc я беру значения данных из оригинала и устанавливаю новые значения в новом массиве, затем копирую указатель. Итак, теперь вы говорите, что память Calloc выделяется, а переменная стека не очищается (хотя это не имеет значения, потому что мне нужна только локальная переменная для вычисления среднего). Это то, что вы имели ввиду?

Sarthak 21.12.2020 12:14

«Очищено» означает «установлено на ноль». «Выпущено» означает «доступно для использования другими». Память стека не очищается, но освобождается при выходе из функции.

Paul Ogilvie 21.12.2020 12:15

@kaylum, Да, он объявлен в другом файле .c (основном файле). И его объявление — это первый блок кода в моем коде (функция calloc). Я просто скопировал этот синтаксис, чтобы выделить temp.

Sarthak 21.12.2020 12:16

@PaulOgilvie, о, спасибо за эту информацию.

Sarthak 21.12.2020 12:18

@kaylum, я добавил это в вопрос.

Sarthak 21.12.2020 12:20
image действительно передается как параметр функции. Таким образом, вы не можете сделать image = temp, так как image в этом случае является локальной переменной, и ее установка не меняет переменную вызывающей стороны.
kaylum 21.12.2020 12:21
image выглядит как , RGBTRIPLE image[height][width]) и передается по значению.
KamilCuk 21.12.2020 12:22

Но это массив... и имя массива само по себе является указателем.

Sarthak 21.12.2020 12:27

Так? Вы можете изменить содержимое, но вы не можете изменить само значение указателя/массива вызывающей стороны.

kaylum 21.12.2020 12:29

Массив не является указателем. Однако он может превратиться в указатель на его первый элемент. И достаточно странно, что объявление аргумента функции «массив» фактически объявляет указатель (т.е. в качестве аргумента RGBTRIPLE image[height][width] действительно будет RGBTRIPLE (*image)[width]).

Some programmer dude 21.12.2020 12:30

И проблема заключается в стандартной проблеме «аргументы передаются по значению», которая часто возникает у новичков. Когда вы вызываете функцию, значение аргумента копируется в переменную аргумента. Изменение переменной-аргумента путем присваивания изменит только локальную переменную-аргумент, а не исходное значение, используемое в вызове. Либо return новое значение, либо проведите небольшое исследование по эмуляции передачи по ссылке в C.

Some programmer dude 21.12.2020 12:32

@Someprogrammerdude, спасибо за разъяснение. и да, я знал о передаче по ссылке, я запутался с массивами и указателями.

Sarthak 21.12.2020 12:34
Почему в Python есть оператор &quot;pass&quot;?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
16
282
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Жаль, что прототип для размытия не дан, но, вероятно, что-то вроде void blur(RGBTRIPLE *image, int width, int height)

Похоже, вы хотите обновить содержимое изображения напрямую, а не создавать новое.

В этом случае ваш последний оператор image = temp присвоит новое значение локальной копии указателя изображения, но не повлияет на исходное значение.

Вы хотите скопировать содержимое temp обратно в изображение с помощью memcpy: memcpy(image, temp, width*height*sizeof(RGBTRIPLE);

После этого вам нужно будет free temp перед возвратом, иначе у вас останется утечка памяти.

Я добавил прототип

Sarthak 21.12.2020 12:28

Синтаксис указателей на массивы немного странный. Вы можете передать указатель на массив высоты и ширины и выделить один массив высоты и ширины:

#include <stdlib.h>
#include <stdio.h>
typedef int RGBTRIPLE;
void blur(int height, int width, RGBTRIPLE (**image)[height][width]) {
    RGBTRIPLE (*temp)[height][width] = calloc(1, sizeof(*temp));
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            (*temp)[i][j] = i * 100 + j;
        }
    }
    *image = temp;
}

int main() {
    int height = 3;
    int width = 4;
    RGBTRIPLE (*arr)[height][width];
    blur(height, width, &arr);
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            printf("[%d][%d] = %d\n", i, j, (*arr)[i][j]);
        }
    }
    free(arr);
}

(Вы можете использовать arr[0][i][j] вместо (*arr)[i][j] для облегчения набора текста и большей путаницы). Вы также можете передать указатель на массив ширины и выделить высоту массивов ширины:

#include <stdlib.h>
#include <stdio.h>
typedef int RGBTRIPLE;
void blur(int height, int width, RGBTRIPLE (**image)[width]) {
    RGBTRIPLE (*temp)[width] = calloc(height, sizeof(*temp));
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            temp[i][j] = i * 100 + j;
        }
    }
    *image = temp;
}

int main() {
    int height = 3;
    int width = 4;
    RGBTRIPLE (*arr)[width];
    blur(height, width, &arr);
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            printf("[%d][%d] = %d\n", i, j, arr[i][j]);
        }
    }
    free(arr);
}

Но в целом, если вы только изучаете язык и у вас нет особых требований к использованию указателя на VLA, я бы настоятельно рекомендовал просто использовать указатель на указатель. Не будьте 3-звездочным программистом и используйте структуру или возвращаемое значение для передачи результата. Такой код будет ясен и понятен любому программисту.

#include <stdlib.h>
#include <stdio.h>
typedef int RGBTRIPLE;
RGBTRIPLE **blur(int height, int width) {
    RGBTRIPLE **temp = calloc(height, sizeof(*temp));
    for (int i = 0; i < height; ++i) {
        temp[i] = calloc(width, sizeof(*temp[i]));
    }

    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            temp[i][j] = i * 100 + j;
        }
    }
    return temp;
}

int main() {
    int height = 3;
    int width = 4;
    RGBTRIPLE **arr = blur(height, width);
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            printf("[%d][%d] = %d\n", i, j, arr[i][j]);
        }
    }

    for (int i = 0; i < height; ++i) {
        free(arr[i]);
    }
    free(arr);
}

В реальном коде я бы предпочел сделать RGBTRIPLE более непрозрачным типом и написать метод доступа к элементу, чтобы он мог проверять доступ за пределами границ и сделать дизайн более объектно-ориентированным, где RGBTRIPLE — это объект, над которым мы работаем. Что-то вдоль:

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

typedef int RGBTRIPLE;

struct rgbtriple_s {
    RGBTRIPLE **vals;
    size_t height;
    size_t width;
};

int rgbtriple_blur(struct rgbtriple_s *t, size_t height, size_t width) {
    RGBTRIPLE **temp = calloc(height, sizeof(*temp));
    // ERROR CHECKING!
    for (size_t i = 0; i < height; ++i) {
        temp[i] = calloc(width, sizeof(*temp[i]));
        // ERROR CHECKING!
    }

    for (size_t i = 0; i < height; ++i) {
        for (size_t j = 0; j < width; ++j) {
            temp[i][j] = i * 100 + j;
        }
    }

    t->vals = temp;
    t->height = height;
    t->width = width;

    return 0;
}

/// Get reference to.
int *rgbtriple_getr(struct rgbtriple_s *t, size_t x, size_t y) {
    assert(x < t->height);
    assert(y < t->width);
    return &t->vals[x][y];
}

int rgbtriple_get(struct rgbtriple_s *t, size_t x, size_t y) {
    return *rgbtriple_getr(t, x, y);
}


void rgbtriple_free(struct rgbtriple_s *t) {
    if (t->vals == NULL) return;
    for (size_t i = 0; i < t->height; ++i) {
        free(t->vals[i]);
    }
    free(t->vals);
    t->vals = NULL;
}

int main() {
    const size_t height = 3;
    const size_t width = 4;
    struct rgbtriple_s data = {0};
    rgbtriple_blur(&data, height, width);
    for (size_t i = 0; i < height; ++i) {
        for (size_t j = 0; j < width; ++j) {
            printf("[%zu][%zu] = %d\n", i, j, rgbtriple_get(&data, i, j));
        }
    }
    rgbtriple_free(&data);
}

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

Мне потребуется некоторое время, чтобы правильно понять этот ответ. Но спасибо за совет, кажется полезным.

Sarthak 21.12.2020 12:44

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