使用 TTF_RenderText() 加载字体时获取段错误 TTF_OpenFontRW()
Getting a segfault using TTF_RenderText() when the font is loaded using TTF_OpenFontRW()?
我的项目正在尝试从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_Font
SDL_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_Close
TTF_Font
之后立即放置delete[] rwbuffer
。
结论
大多数情况下,当您遇到段错误时,这意味着您要么访问未分配的内存,要么访问释放的内存。
因此,每当您遇到像您这样的案例时,请尝试评论您的关闭、免费和删除呼叫,看看它是否能修复它。
不过,不要让他们永远评论,这可能会导致内存泄漏!如果其中一个调用负责您的段错误,请尝试找出原因(或者如果您无法弄清楚原因,请在 Stack Overflow 上询问),并将负责段错误的调用放在其他地方,实际上属于哪里。
如果使用数组,请检查索引以查看读取的距离是否不超过数组的大小,因为这也可能导致段错误。
- 为什么PyImport_ImportModule python 3.7.2 中出现段错误?
- 为什么在访问 vtkRenderWindow 的"交互器"变量时会发生段错误?
- 全局向量导致 C++ 程序结束时出现段错误
- 为什么重载运算符<<打印特征类成员会导致段错误?
- 更改条件段错误
- 使用 TTF_RenderText() 加载字体时获取段错误 TTF_OpenFontRW()
- 注册对对象工厂的调用会导致段错误
- pthread_create在构造函数段错误中
- Nanoflann发现邻居提出段错误
- C++ 中的构造函数、继承、堆栈、堆、this-pointer 和段错误
- 具有unique_ptr的 CRTP 会导致段错误
- 增强纤维work_stealing屏障会导致段错误
- 当我返回指向结构的指针向量时出现段错误
- C++为什么我的代码没有爆炸/段错误?
- Pthread段错误,使用指向main中变量的指针
- C++ 模板中的段错误
- 相当于Windows/MSVC上的段错误?
- 为什么我的 LLVM JIT 实现出现段错误?
- 为什么访问我的引用捕获变量会导致我的 lambda 函数出现段错误?
- 为什么自删除的全局 Vulkan 实例仅在添加层时才导致段错误?