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

Те, кто знаком с проблемой фильтра 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
Стоит ли изучать 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
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

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