使用 TTF_RenderText() 加载字体时获取段错误 TTF_OpenFontRW()

Getting a segfault using TTF_RenderText() when the font is loaded using TTF_OpenFontRW()?

本文关键字:TTF 段错误 错误 OpenFontRW 获取 加载 RenderText 使用 字体      更新时间:2023-10-16

我的项目正在尝试从zip文件加载字体进行渲染。我使用 LibZip 访问 zip,我使用的过程适用于我正在渲染的所有图形。但是,当我尝试使用 SDL_ttf 的 TTF_RenderText 函数呈现文本时,使用相同的过程加载 ttf 字体最终会导致段错误。我已经测试了这是原因,用 TTF_Open 替换加载代码并成功加载它,而使用我的代码从 zip 加载以段错误结尾。这是我用来从zip加载文件的代码:

TTF_Font* m_load_font(const char* filename, int fontsize) {
zip_stat_t filestat;
uint64_t filetotal;
SDL_RWops* rwop = SDL_AllocRW();
//Check to see if the data stream got allocated
if ( rwop == NULL ) {
u_error(std::cerr, CC_ERROR_SDL, "SDL_AllocRW");
SDL_FreeRW(rwop);
return nullptr;
}
//Check to see if the resource zip is open
if ( cc_zip_res == NULL ) {
std::cerr << "Font Load Error: Zip File Not Loaded" << std::endl;
SDL_FreeRW(rwop);
return nullptr;
}
//Open the file
zip_file_t* file = zip_fopen(cc_zip_res, filename, 0);
if (file == NULL) {
u_error(std::cerr, CC_ERROR_ZIP, "zip_fopen");
zip_fclose(file);
SDL_FreeRW(rwop);
return nullptr;
}
//Read the file
if(zip_stat(cc_zip_res, filename, 0, &filestat) == -1) {
u_error(std::cerr, CC_ERROR_ZIP, "zip_stat");
zip_fclose(file);
SDL_FreeRW(rwop);
return nullptr;
}
//Write data to a data holding array
char* rwbuffer = new char[filestat.size];
rwop = SDL_RWFromMem(rwbuffer, filestat.size);
while(filetotal < filestat.size) {
char buffer[256];
int64_t length;
//read the file into the buffer
length = zip_fread(file, buffer, 256);
if (length == -1) {
u_error(std::cerr, CC_ERROR_ZIP, "zip_fread");
zip_fclose(file);
SDL_FreeRW(rwop);
delete[] rwbuffer;
return nullptr;
}
//write the buffer into the rwop stream
if ( (size_t)length != SDL_RWwrite(rwop, buffer, 1, (size_t)length) ) {
u_error(std::cerr, CC_ERROR_SDL, "SDL_RWwrite");
zip_fclose(file);
SDL_FreeRW(rwop);
delete[] rwbuffer;
return nullptr;
}
//Increment the count so that the loop ends
filetotal += length;
}
zip_fclose(file);
//Return the seek pointer of the RWop to the beginning so that the file can be read
SDL_RWseek(rwop,0,RW_SEEK_SET);
//Load the font from the rwop
TTF_Font* temp = TTF_OpenFontRW(rwop, 0, fontsize);
SDL_FreeRW(rwop);
delete[] rwbuffer;
if (temp == NULL) {
u_error(std::cerr, CC_ERROR_TTF, "TTF_OpenFontRw");
}
return temp;
}

这是我使用的代码,它有TTF_RenderText:

bool CC_Texture::load_from_font(TTF_Font* font, std::string* text, SDL_Color color,
SDL_Renderer* ren) {
if (font == NULL) {
std::cerr << "Font is Null in CC_Texture::load_from_font" << std::endl;
return false;
}
//The wrap length is an estimate, will figure out the exact
SDL_Surface* surf = TTF_RenderText_Blended_Wrapped(font, text->c_str(), color, 600);
if (surf == NULL) {
u_error(std::cerr, CC_ERROR_TTF, "TTF_RenderText_Blended_Wrapped");
return false;
}
texture = std::shared_ptr<SDL_Texture>(SDL_CreateTextureFromSurface(ren, surf),
SDL_DestroyTexture);
if (texture.get() == NULL) {
u_error(std::cerr, CC_ERROR_SDL, "SDL_CreateTextureFromSurface");
return false;
}
return true;
}

编辑:我还应该说,在加载后,我使用调试器转储了rwbuffer的内容,它是一个有效的字体文件,所以似乎zip读取不是问题。

TD;博士

问题所在

使用TTF_OpenFontRW打开SDL_RWops及其缓冲区后立即释放了它们,即使这样的TTF_Font与它们密切相关,释放它们本质上意味着破坏它,因为它会使TTF_Font尝试访问您释放的内存,从而导致段错误错误。

解决方案

m_load_font函数中删除SDL_FreeRW(rwop);delete[] rwbuffer;行,并将它们放在关闭字体的任何位置。

为了简化事情,您可以将TTF_OpenFontRW的第二个参数设置为1,而不是0在关闭字体时自动关闭rwop,但您仍然需要在关闭字体后立即手动delete[] rwbuffer;

注意

我让我的 RWops 保持活力,而不是释放它。

确保保持 RWops链接到它的缓冲区都处于活动状态。仅靠 RWops 是不够的,只要您使用字体,就不得释放 RWops 和您打开它的缓冲区。

长答案

SDL_RWOps如何工作

文档内容

根据SDL_RWFromMem函数的文档(镜像链接):

此内存缓冲区不会被SDL_RWops复制;您提供的指针必须保持有效,直到您关闭流。关闭流不会释放原始缓冲区。

这对我们意味着什么?

这意味着只有在使用完该缓冲区后,您才能释放链接到SDL_RWops的缓冲区。否则,它将使您的SDL_RWops实例无效,因为它将尝试读取已读取的内存,从而导致分段错误。

TTF_OpenFontRW如何工作

文档的含义

对于此功能,文档(镜像链接)如下:

如果freesrc不为零,则无论此函数是否成功,SDL_RWops都将在返回之前关闭。 在任何情况下,SDL_ttf在此调用期间从SDL_RWops读取所需的所有内容。

我们在这里并不真正关心句子的freesrc部分,我们关心的是另一部分,它说无论如何(暗示无论参数如何),TTF_OpenFontRW函数都会从SDL_RWops读取它需要的所有内容,这意味着调用这个函数后不再需要SDL_RWops, 右?

事实证明,这种说法是错误的。见下文。

TTF_OpenFontRW的实际实现

在撰写本文时,以下是TTF_OpenFontRW函数的实现方式:

  • TTF_OpenFontRW将其调用重定向到TTF_OpenFontIndexRW
  • TTF_OpenFontIndexRW将其调用重定向到TTF_OpenFontIndexDPIRW
  • TTF_OpenFontIndexDPIRW实现如下。

如第 1800 行所示,指向传递给函数的SDL_RWops的指针存储在将返回给您的TTF_Font实例中。

在第 1812 行,我们还可以看到它存储在流的结构中,该流本身存储在第 1817 行的TTF_Font实例中。

所以:

在任何情况下,SDL_ttf都会在此调用期间从SDL_RWops读取所需的一切。

这种说法是错误的。

但还有更多,正如我们在第 1769 到 1771 行、第 1784 行到 1786 行和 1793 到 1795 行看到的那样,如果freesrc参数设置为 true,则SDL_RWops实例将被关闭,但前提是存在错误。如果没有错误,它永远不会关闭!

但是,在第 1801 行,我们可以看到freesrc布尔值存储在TTF_Font实例中,正如在第 2754 行中看到的那样,这是在完成字体处理后将执行的TTF_Close调用期间关闭SDL_RWops

所以:

如果freesrc不为零,则无论此函数是否成功,SDL_RWops都将在返回之前关闭。

这种说法是错误的,只有当函数不成功时,SDL_RWops才会关闭。

这对我们意味着什么?

TTF_OpenFontRW函数调用返回的TTF_Font实例与您传递给它的SDL_RWops密切相关。此实例将要求您SDL_RWops保持打开状态,只要您保持字体打开状态。

到目前为止我们所知道的摘要

到目前为止,我们已经弄清楚:

  • 如果在执行此操作之前未关闭SDL_RWops,则无法释放链接到SDL_RWops的缓冲区。否则,您的SDL_RWops将失效。
  • 如果您之前未关闭链接到TTF_FontSDL_RWops,则无法关闭TTF_Font。否则,您的TTF_Font将失效。

你在所有这些方面的错误

让我写回您的m_load_font函数的内容,简化:

TTF_Font* m_load_font(const char* filename, int fontsize) {
zip_stat_t filestat;
uint64_t filetotal;
SDL_RWops* rwop = SDL_AllocRW();
/*
---
Code to open the TTF file located in your ZIP
---
*/

//Write data to a data holding array
char* rwbuffer = new char[filestat.size];
rwop = SDL_RWFromMem(rwbuffer, filestat.size);
/*
---
Code to write the content of the TTF file you opened into rwbuffer
---
*/
//Load the font from the rwop
TTF_Font* temp = TTF_OpenFontRW(rwop, 0, fontsize);
SDL_FreeRW(rwop);
delete[] rwbuffer;
if (temp == NULL) {
u_error(std::cerr, CC_ERROR_TTF, "TTF_OpenFontRw");
}
return temp;
}

在这里,如您所见,通过将rwop传递给TTF_OpenFontRW呼叫打开TTF_Font后,您可以通过呼叫SDL_FreeRW来释放rwop。 如果您关闭其SDL_RWops,您的TTF_Font将无法使用,这仅在您不再需要字体时关闭后才需要完成。

但这还不是全部,即使您没有关闭SDL_RWops,也可以删除rwbuffer,这是rwop链接到的缓冲区。 因此,即使不关闭SDL_RWops,也可以删除保存其内容的缓冲区,使其无法使用。

解决方案

使代码正常工作

应删除SDL_RWops结束行和 和 缓冲区delete[]行,如下所示:

TTF_Font* m_load_font(const char* filename, int fontsize) {
zip_stat_t filestat;
uint64_t filetotal;
SDL_RWops* rwop = SDL_AllocRW();
/*
---
Code to open the TTF file located in your ZIP
---
*/

//Write data to a data holding array
char* rwbuffer = new char[filestat.size];
rwop = SDL_RWFromMem(rwbuffer, filestat.size);
/*
---
Code to write the content of the TTF file you opened into rwbuffer
---
*/
//Load the font from the rwop
TTF_Font* temp = TTF_OpenFontRW(rwop, 0, fontsize);
if (temp == NULL) {
u_error(std::cerr, CC_ERROR_TTF, "TTF_OpenFontRw");
}
return temp;
}

避免内存泄漏

以您组织代码的方式,它会导致内存泄漏,因为您不会在关闭字体时将SDL_RWops保存在任何地方以关闭它,也不会将rwbuffer的值保存为指针以在关闭其相应的SDL_RWops后立即将其删除。

这里有一个关于如何修复它的建议,尽管由你来决定什么最适合你的需求。

首先,我会将TTF_OpenFontRW(rwop, 0, fontsize)更改为TTF_OpenFontRW(rwop, 1, fontsize),以便告诉字体在您调用TTF_Close释放它时立即自动关闭TTF_Font持有的SDL_RWops

但是关闭SDL_RWops不会释放它链接到的缓冲区。 因此,我会跟踪rwbuffer作为指针的值,以便在您TTF_CloseTTF_Font之后立即放置delete[] rwbuffer

结论

大多数情况下,当您遇到段错误时,这意味着您要么访问未分配的内存,要么访问释放的内存。

因此,每当您遇到像您这样的案例时,请尝试评论您的关闭、免费和删除呼叫,看看它是否能修复它。

不过,不要让他们永远评论,这可能会导致内存泄漏!如果其中一个调用负责您的段错误,请尝试找出原因(或者如果您无法弄清楚原因,请在 Stack Overflow 上询问),并将负责段错误的调用放在其他地方,实际上属于哪里。

如果使用数组,请检查索引以查看读取的距离是否不超过数组的大小,因为这也可能导致段错误。