Массив SDL Surfaces меняет содержимое после определенного количества перераспределений - PullRequest
0 голосов
/ 01 февраля 2020

Недавно я пытался изучить SDL, графическую библиотеку, среди прочего, для C. Я не продвинулся слишком далеко, но с основами, которые я изучил из этого урока (Да, я знаю, что это для C ++, но кажется, что большинство вещей все еще то же самое), я попытался создайте «шаблонную» SDL-программу для запуска всех моих будущих программ. Вот что у меня было:

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

int initProg(SDL_Window **window, SDL_Surface **surface, char *name, int x, int y, int w, int h, int flags);
void mainLoop(SDL_Window *window, SDL_Surface *surface);
void closeProg(SDL_Window *window);
SDL_Surface *loadImage(char *path);

int main(int argc, char *args[]) {
    SDL_Window *window = NULL;
    SDL_Surface *surface = NULL;
    char windowName[13] = "SDL Tutorial";
    int windowXPos = SDL_WINDOWPOS_UNDEFINED;
    int windowYPos = SDL_WINDOWPOS_UNDEFINED;
    int windowWidth = 600;
    int windowHeight = 600;
    int flags = SDL_WINDOW_SHOWN;
    if (!initProg(&window, &surface, windowName, windowXPos, windowYPos, windowWidth, windowHeight, flags)) {
        return 1;
    }

    mainLoop(window, surface);

    closeProg(window);

    return 0;
}

int initProg(SDL_Window **window, SDL_Surface **surface, char *name, int x, int y, int w, int h, int flags) {
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        printf("Failed to initialize SDL.\nError: %s\n", SDL_GetError());
        return 0;
    } else {
        *window = SDL_CreateWindow(name, x, y, w, h, flags);
        if (*window == NULL) {
            printf("Failed to create a window.\nError:%s\n", SDL_GetError());
            return 0;
        } else {
            *surface = SDL_GetWindowSurface(*window);
            return 1;
        }
    }
}

void mainLoop(SDL_Window *window, SDL_Surface *surface) {
    // Simple program to fade between white and black background
    int g = 0;
    int diff = -1;

    int quit = 0;
    SDL_Event e;
    while (!quit) {
        while(SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = 1;
            }
        }

        SDL_FillRect(surface, NULL, SDL_MapRGB(surface->format, g, g, g));
        SDL_UpdateWindowSurface(window);

        if (g == 0 || g == 255) {
            diff *= -1;
        }
        g += diff;

        SDL_Delay(10);
    }
}

void closeProg(SDL_Window *window) {
    SDL_DestroyWindow(window);
    window = NULL;

    SDL_Quit();
}

Программа разделена на три секции: одна для инициализации SDL, одна для основной программы и одна для закрытия SDL и освобождения поверхностей.

Я понял, что проблема в том, что если бы я создал другую поверхность, мне пришлось бы добавить код в функцию closeProg(), чтобы освободить поверхность в конце. NB: Имейте в виду, что эти "поверхности" являются просто указателями на фактическую вещь, с которой, насколько я знаю, вы на самом деле не взаимодействуете, вы просто имеете дело с указателем на нее.

Чтобы обойти это, я решил создать массив этих указателей на поверхности, а затем создать функцию, которая создает поверхность и добавляет ее указатель на массив. Это позволило бы мне в конце функции closeProg() go пройти через каждую поверхность и освободить ее, а затем освободить массив. (Обратите внимание, что поверхность окна не добавляется в этот список, поскольку она автоматически освобождается с помощью функции SDL_DestroyWindow())

Вот объявление этого нового массива:

// The pointer to the array. Currently NULL until something is added
SDL_Surface **surfaces = NULL;
// Keeps track of the size of the array
size_t surfaceCount = 0;

Это функция, добавляющая в массив:

int addSurface(SDL_Surface *surface, SDL_Surface **surfaces, size_t *surfaceCount) {
    size_t new_count = *surfaceCount + 1;
    SDL_Surface **temp = realloc(surfaces, sizeof(*temp) * new_count);
    if (temp == NULL) {
        printf("Failed to reallocate to %d bytes of memory.", sizeof(*temp) * new_count);
        return 0;
    } else {
        surfaces = temp;
        *(surfaces + new_count - 1) = surface;
        *surfaceCount = new_count;
        return 1;
    }

}

И здесь она используется. Обратите внимание, что loadImage() возвращает поверхность изображения, а showSurfaces() печатает содержимое массива surfaces.

// Check contents before adding anything
showSurfaces(surfaces, *surfaceCount);
// Add an image
SDL_Surface *fire = loadImage("./fire.bmp");
addSurface(fire, surfaces, surfaceCount);
// Check contents again
showSurfaces(surfaces, *surfaceCount);

// Add another image and check contents again
SDL_Surface *ice = loadImage("./ice.bmp");
addSurface(ice, surfaces, surfaceCount);
showSurfaces(surfaces, *surfaceCount);

// Add another image and check contents a final time
SDL_Surface *man = loadImage("./man.bmp");
addSurface(man, surfaces, surfaceCount);
showSurfaces(surfaces, *surfaceCount);

До первых двух поверхностей все прошло хорошо. Однако, когда я попытался добавить третью поверхность, программа зависла непосредственно перед закрытием (указывает, что что-то не так в функции closeProg()?). Я решил распечатать содержимое массива, и вот что я получил.

No current surfaces.

Current surfaces:
        Index 0: 00753C98


Current surfaces:
        Index 0: 00753C98
        Index 1: 00754780


Current surfaces:
        Index 0: 02805150
        Index 1: 008F00C0
        Index 2: 201339FC

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

Я думаю, именно поэтому программа зависает при закрытии, потому что в функции closeProg программе сказано освободить неизвестный указатель, который не является поверхностью следовательно, он падает. Не говоря уже о том, что я также установил этот указатель на NULL, и кто знает, что еще это может вызвать.

Является ли это изменение содержимого массива нормальным? И если нет, я был бы очень признателен, если бы вы могли помочь мне найти причину этого странного поведения. Я повторяю, что я ПОЛНЫЙ НАЧИНАЮЩИЙ в этом и поэтому любая помощь, даже в вопросах, не связанных с этим вопросом, будет ОТЛИЧНО оценена. Спасибо вам в advance:)


Для справки, вот изображения, которые я использовал .

Вот полный код, если вам интересно:

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

int initProg(SDL_Window **window, SDL_Surface **surface, char *name, int x, int y, int w, int h, int flags);
void mainLoop(SDL_Window *window, SDL_Surface *surface, SDL_Surface **surfaces, size_t *surfaceCount);
void closeProg(SDL_Window *window, SDL_Window **surfaces, size_t surfaceCount);
SDL_Surface *loadImage(char *path);
int addSurface(SDL_Surface *surface, SDL_Surface **surfaces, size_t *surfaceCount);

int main(int argc, char *args[]) {
    SDL_Window *window = NULL;
    SDL_Surface *surface = NULL;

    // The pointer to the array. Currently NULL untill something is added
    SDL_Surface **surfaces = (SDL_Surface *)calloc(1, sizeof(*surfaces));
    // Keeps track of the size of the array
    size_t surfaceCount = 0;

    char windowName[13] = "SDL Tutorial";
    int windowXPos = SDL_WINDOWPOS_UNDEFINED;
    int windowYPos = SDL_WINDOWPOS_UNDEFINED;
    int windowWidth = 600;
    int windowHeight = 600;
    int flags = SDL_WINDOW_SHOWN;
    if (!initProg(&window, &surface, windowName, windowXPos, windowYPos, windowWidth, windowHeight, flags)) {
        return 1;
    }

    mainLoop(window, surface, surfaces, &surfaceCount);

    closeProg(window, surfaces, surfaceCount);

    return 0;
}

int initProg(SDL_Window **window, SDL_Surface **surface, char *name, int x, int y, int w, int h, int flags) {
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        printf("Failed to initialize SDL.\nError: %s\n", SDL_GetError());
        return 0;
    } else {
        *window = SDL_CreateWindow(name, x, y, w, h, flags);
        if (*window == NULL) {
            printf("Failed to create a window.\nError:%s\n", SDL_GetError());
            return 0;
        } else {
            *surface = SDL_GetWindowSurface(*window);
            return 1;
        }
    }
}

void mainLoop(SDL_Window *window, SDL_Surface *surface, SDL_Surface **surfaces, size_t *surfaceCount) {
    // Simple program to fade between white and black background
    int g = 0;
    int diff = -1;

    // Check contents before adding anything
    showSurfaces(surfaces, *surfaceCount);
    // Add an image
    SDL_Surface *fire = loadImage("./fire.bmp");
    addSurface(fire, surfaces, surfaceCount);
    // Check contents again
    showSurfaces(surfaces, *surfaceCount);

    // Add another image and check contents again
    SDL_Surface *ice = loadImage("./ice.bmp");
    addSurface(ice, surfaces, surfaceCount);
    showSurfaces(surfaces, *surfaceCount);

    // Add another image and check contents a final time
    SDL_Surface *man = loadImage("./man.bmp");
    addSurface(man, surfaces, surfaceCount);
    showSurfaces(surfaces, *surfaceCount);

    int quit = 0;
    SDL_Event e;
    while (!quit) {
        while(SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = 1;
            }
        }

        SDL_FillRect(surface, NULL, SDL_MapRGB(surface->format, g, g, g));
        SDL_UpdateWindowSurface(window);

        if (g == 0 || g == 255) {
            diff *= -1;
        }
        g += diff;

        SDL_Delay(10);
    }
}

void closeProg(SDL_Window *window, SDL_Window **surfaces, size_t surfaceCount) {
    // Go through the array and free each surface.
    for (int i = 0; i < surfaceCount; i++){
        SDL_FreeSurface(*(surfaces + i));
        *(surfaces + i) = NULL;
    }
    // Free the array itself.
    free(surfaces);

    SDL_DestroyWindow(window);
    window = NULL;

    SDL_Quit();
}

SDL_Surface *loadImage(char *path) {
    SDL_Surface *image = SDL_LoadBMP(path);
    if (image == NULL) {
        printf("Failed to load image.\nError: %s\n", SDL_GetError());
    }
    return image;
}

int addSurface(SDL_Surface *surface, SDL_Surface **surfaces, size_t *surfaceCount) {
    size_t new_count = *surfaceCount + 1;
    SDL_Surface **temp = realloc(surfaces, sizeof(*temp) * new_count);
    if (temp == NULL) {
        printf("Failed to reallocate to %d bytes of memory.", sizeof(*temp) * new_count);
        return 0;
    } else {
        surfaces = temp;
        *(surfaces + new_count - 1) = surface;
        *surfaceCount = new_count;
        return 1;
    }

}

void showSurfaces(SDL_Surface **surfaces, size_t surfaceCount) {
    if (surfaceCount == 0) {
        printf("\nNo current surfaces.\n");
    } else {
        printf("\nCurrent surfaces:\n");
        for (int i = 0; i < surfaceCount; i++) {
            printf("\tIndex %d: %p\n", i, *(surfaces + i));
        }
        putchar('\n');
    }
}

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

ПРИМЕЧАНИЕ: я использую SDL 2.0

1 Ответ

1 голос
/ 01 февраля 2020

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

На самом деле, нет. Это проще всего сделать, но можно рассчитывать, что ОС освободит память программы, включая любую оставшуюся динамически распределенную память, когда программа завершится. Вы можете с легкостью удерживать выделенную память до этого момента, вместо того, чтобы явно освобождать ее непосредственно перед завершением.

В любом случае,

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

Я думаю, именно поэтому программа зависает при закрытии, потому что в функции closeProg программе сказано освободить неизвестный указатель, который не является поверхностью следовательно, он падает. Не говоря уже о том, что я также установил этот указатель на NULL, и кто знает, что еще это может вызвать.

Я думаю, что ваш анализ правдоподобен.

Есть это изменение содержимого массива нормально?

Нет. Перераспределение с realloc() сохраняет содержимое исходного пространства, копируя его в новое пространство, если оно не перекрывается, вплоть до меньших размеров двух пространств.

Я был бы очень признателен если бы вы могли помочь мне найти причину этого странного поведения.

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

В частности, рассмотрим этот отрывок:

        surfaces = temp;
        *(surfaces + new_count - 1) = surface;
        *surfaceCount = new_count;

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

Ситуация для указателя surfaces не отличается. Когда в первой строке выписки вы назначаете surfaces, вы не вносите изменения, которые будут видны звонящему. Вы только изменяете значение локальной переменной внутри функции. Вы должны либо добавить слой косвенности для surface, либо передать новый указатель обратно вызывающей стороне через возвращаемое значение функции. Лично я бы go с последним, потому что трех- * программирование не является общепринятым как хороший стиль.

...