![](/img/trans.png)
[英]SDL2 SDL_CreateRenderer downgrades the OpenGL context, can it be prevented?
[英]Overloading new/delete operators on ubuntu 22.04, libc 2.35 and calling SDL2 SDL_CreateRenderer causes segmentation fault
我在 linux (ubuntu) 中有自己的內存管理器,以便檢測我的 c/c++ 代碼中的內存泄漏或內存損壞。
要使用我自己的內存管理器,我已經重載了這樣的新/刪除全局運算符,
void *operator new (size_t _size){
return malloc(_size);
}
void operator delete(void *_pointer){
free(_pointer);
}
代碼 1.1
但是為了添加一些指針信息,我為頭元數據保留了額外的內存,例如,在這個結構中保存分配器類型和分配的大小,
typedef struct{
size_t size;
ALLOCATOR_TYPE allocator_type
}PointerHeaderInformation;
其中 ALLOCATOR_TYPE 是,
typedef enum{
UNKNOWN_ALLOCATOR=0,
NEW_ALLOCATOR,
NEW_ALLOCATOR_WITH_BRACETS,
MAX_ALLOCATE_TYPES
}ALLOCATOR_TYPE;
代碼 1.2
所以我修改了代碼 1.1 中看到的新運算符來分配指針和設置頭信息,正如我們在下面的代碼中看到的那樣,
void *operator new(size_t _size){
// set size for request _size and header information
size_t size=sizeof(HeaderPointerInformation)+_size;
// here the pointer allocation
char *pointer=(char *)malloc(size);
if(pointer==NULL){
throw std::bad_alloc();
}
// setup header information
((HeaderPointerInformation *)(pointer))->type_allocator=NEW_ALLOCATOR;
((HeaderPointerInformation *)(pointer))->size=_size;
// returns the pointer + sizeof(HeaderPointerInformation)
return pointer+sizeof(HeaderPointerInformation);
}
代碼 1.3
正如我們在代碼 1.3 中看到的,它返回分配的指針 + sizeof(HeaderPointerInformation) 以不覆蓋標頭信息。
另一方面,當我們想要使用自定義分配器釋放分配的指針時,它必須首先獲取標頭指針,檢查分配器的類型並使用free
函數釋放指針。 例如對於重載的delete
運算符,
void operator delete(void *pointer) noexcept{
// check NULL
if(pointer == NULL){
return;
}
// get header information
HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-SIZEOF_ALIGNED_HEADER);
// check type allocator
if(header->type_allocator != NEW_ALLOCATOR){
fprintf(stderr,"pointer was not allocated as 'new' allocator\n");
}
// header is the base of allocated pointer so it's freed
free(header);
}
代碼 1.4
當我嘗試使用 SDL_CreateRenderer 創建渲染器時,我正在嘗試與 SDL2 和重載的新/刪除運算符結合使用時發生的異常分段錯誤。 SDL_CreateRenderer 執行一系列調用,直到llvm::Regex::Regex() 中的分段錯誤發生之前最后已知的第 70 行 dl-init.c,如下圖所示。
我以前的 ubuntu 發行版、22.04、18.04 等從來沒有遇到過任何問題,但是在最近的 ubuntu 發行版 22.04 中,它帶有 libc 2.35,所以 libc 中的某些內容發生了變化。
這個分段錯誤與重載運算符 new/delete 運算符有關,因為如果我不使用重載運算符,問題就消失了。
我創建了一個非常簡單的代碼來產生這個分段錯誤,
#include <SDL2/SDL.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <new>
#include <cstddef>
// type of allocators
typedef enum{
UNKNOWN_ALLOCATE=0,
NEW_ALLOCATOR,
NEW_ALLOCATOR_WITH_BRACETS,
MAX_ALLOCATE_TYPES
}ALLOCATOR_TYPE;
// metadata information included in the pointer allocated
typedef struct{
char type_allocator;
int size;
}HeaderPointerInformation;
void *newPointer(size_t _size, ALLOCATOR_TYPE _allocator_type){
size_t size=sizeof(HeaderPointerInformation)+_size;
char *pointer=(char *)malloc(size);
if(pointer==NULL){
throw std::bad_alloc();
}
((HeaderPointerInformation *)(pointer))->type_allocator=_allocator_type;
((HeaderPointerInformation *)(pointer))->size=size;
// returns the pointer base (allocated pointer with offset of metadata information)
return pointer+sizeof(HeaderPointerInformation);
}
void* operator new(size_t _size) {
return newPointer(_size,NEW_ALLOCATOR);
}
//--------------------------------------------------------------------------------------------
void* operator new[](size_t _size) {
return newPointer(_size,NEW_ALLOCATOR_WITH_BRACETS);
}
//--------------------------------------------------------------------------------------------
void operator delete(void *pointer) noexcept{
// return if pointer==NULL
if(pointer == NULL){
return;
}
// get header information
HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-sizeof(HeaderPointerInformation));
// check type allocator
if(header->type_allocator != NEW_ALLOCATOR){
fprintf(stderr,"pointer was not allocated as 'new' allocator\n");
}
// header is the base of allocated pointer so it's freed
free(header);
}
//--------------------------------------------------------------------------------------------
void operator delete[](void *pointer) noexcept{
// return if pointer==NULL
if(pointer == NULL){
return;
}
// get header information
HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-sizeof(HeaderPointerInformation));
// check type allocator
if(header->type_allocator != NEW_ALLOCATOR_WITH_BRACETS){
fprintf(stderr,"pointer was not allocated as 'new []' allocator\n");
}
// header is the base of allocated pointer so it's freed
free(header);
}
int main(int argc, char *argv[]){
printf(
"align(std::max_align_t):%i\n"
"sizeof(HeaderPointerInformation):%i\n"
,(int)(alignof(std::max_align_t))
,(int)(sizeof(HeaderPointerInformation))
);
//--------------------------------------------------
// BEGIN: test that overloaded new/delete works as expected
int *i=new int;
int *ii=new int[2];
delete i;
delete [] ii;
// END: test that overloaded new/delete works as expected
//--------------------------------------------------
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
fprintf(stderr,"Unable to init video subsystem: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
SDL_Window *window = SDL_CreateWindow("test",50,50,100,100,0);
if (!window) {
fprintf(stderr,"Unable to create window: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
// Calling SDL_CreateRenderer it causes segmentation fault at llvm::Regex::Regex
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_WINDOW_SHOWN | SDL_RENDERER_ACCELERATED);
if (!renderer) {
fprintf(stderr,"Unable to create renderer: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
if(renderer != NULL){
SDL_DestroyRenderer(renderer);
}
if(window != NULL){
SDL_DestroyWindow(window);
}
}
代碼 1.5
正如我們在代碼 1.5 中看到的,首先是 delete/new 運算符實現,然后在主代碼中它使用重載運算符 new/delete 執行測試以檢查其功能,剩下的只是 SDL2 初始化。
編譯就像下面一樣簡單,
g++ main.cpp -Ipath_include_sdl2 -Lpath_lib_sdl2 -lSDL2 -lm -o main
注意:如有必要,您必須提供path_include_sdl2和path_lib_sdl2
為什么不像一些評論所建議的那樣使用 gnu 編譯器本身附帶的地址清理器(又名 ASAN)工具?
我還不知道我是否會使用 ASAN 進行切換,但原則上我不會,因為我的代碼也是通過 mingw 在 Windows 上編譯的,而 AFAIK mingw ASAN 尚未實現。
編輯關於 user17732522(評論)我有更改代碼 1.5,
malloc
后指針的結果為 NULL,我添加了條件以拋出 sdt::bad_alloc,void *newPointer(size_t _size, ALLOCATOR_TYPE _allocator_type){ ... char *pointer=(char *)malloc(size); if(pointer==NULL){ throw std::bad_alloc(); } ... }
noexcept(true)
更改為noexcept
,void operator delete(void *pointer) noexcept{ ... } void operator delete[](void *pointer) noexcept{ ... }
固定點6,把free((void *)header)
改成了free(header)
固定點 7,在重載函數delete上添加了如果傳遞的指針為 NULL 則返回的條件
void operator delete(void *pointer) noexcept{ // check NULL if(pointer == NULL){ return; } ... } void operator delete[](void *pointer) noexcept{ // check NULL if(pointer == NULL){ return; } ... } void operator delete[](void *pointer) noexcept{ ... }
多個問題:
您不確保從operator new
替換返回的指針至少與__STDCPP_DEFAULT_NEW_ALIGNMENT__
一樣嚴格對齊,此重載必須始終保證這一點。 即使malloc
保證返回適當對齊的內存,您的偏移量也可能會更改您返回的指針的對齊方式。 但是malloc
也可能沒有充分對齊。 malloc
保證至少與alignof(std::max_align_t)
對齊,它可能小於__STDCPP_DEFAULT_NEW_ALIGNMENT__
。
您正在替換的operator new
重載不得返回空指針。 您需要檢查malloc
的結果,如果它是空指針,您必須拋出std::bad_alloc
。 因為您正在取消引用您的實現中的指針,這更直接重要。
不太嚴重的問題:
您訪問標頭結構的方式是別名違規。 您的標頭結構是一種隱式生命周期類型,因此您不一定需要顯式地對其進行new
,但是您需要std::launder
指針才能訪問它。 這對於operator new
來說已經足夠了,但是我認為沒有任何嚴格符合標准的方法來實現operator delete
以便能夠訪問標頭,所以我想你需要依賴編譯器在這里表現得“合理”反正。
如果您為沒有對齊參數的重載替換operator new
和operator delete
,您可能還想用std::align_val_t
參數替換它們,以防程序分配的內存對齊要求比__STDCPP_DEFAULT_NEW_ALIGNMENT__
更嚴格。
釋放函數上的noexcept(true)
是多余的。 默認情況下它們是noexcept(true)
。 這也被規范地寫成noexcept
,而不是noexcept(true)
。
free((void void*
free((void *)header);
中的 void* 是多余的。 它隱含地工作(並且您在operator new
的返回語句中使用它)。
(編輯)其他嚴重問題:
operator delete
重載必須接受空指針值作為參數。 您當前的實現沒有,因為它立即對指針進行指針運算。 在嘗試訪問標頭之前,您需要檢查空指針並在這種情況下從函數返回。同樣在您嘗試做的事情的上下文中:已經有提供您嘗試實現的功能的工具,例如 ASAN 和 valgrind。
正如 user17732522 指出的那樣,返回的指針必須在塊中正確對齊,因為alignof(std::max_align_t)
在我的機器中是16
。
在重載 operator new 的情況下,在通過malloc
分配指針(返回完美且對齊的指針)后,我返回一個指針作為指針基數,偏移量為sizeof(HeaderPointerInformation)
,如果為8
,則它返回一個未對齊的指針。
因此,要解決此問題,必須計算一個有效的偏移量轉換sizeof(HeaderPointerInformation)
作為alignof(std::max_align_t)
的倍數,就像它描述的以下行一樣,
#define SIZEOF_ALIGNED_HEADER (sizeof(HeaderPointerInformation)/BLOCK_ALIGNMENT+1)*BLOCK_ALIGNMENT
BLOCK_ALIGMENT 確實定義為,
#define BLOCK_ALIGNMENT alignof(std::max_align_t)
最后,我展示了重載函數返回/獲取具有正確偏移量的指針的修改代碼 1.5,
#define BLOCK_ALIGNMENT alignof(std::max_align_t) #define SIZEOF_ALIGNED_HEADER (sizeof(HeaderPointerInformation)/BLOCK_ALIGNMENT+1)*BLOCK_ALIGNMENT void *newPointer(size_t _size, ALLOCATOR_TYPE _allocator_type){ size_t size=SIZEOF_ALIGNED_HEADER+_size; char *pointer=(char *)malloc(size); ... // returns the pointer base (allocated pointer with offset of metadata information) return pointer+SIZEOF_ALIGNED_HEADER; } void operator delete(void *pointer) noexcept{ ... // get header information HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-SIZEOF_ALIGNED_HEADER); ... // header is the base of allocated pointer so it's freed free(header); } void operator delete[](void *pointer) noexcept{ ... // get header information HeaderPointerInformation *header=(HeaderPointerInformation *)((char *)pointer-SIZEOF_ALIGNED_HEADER); ... // header is the base of allocated pointer so it's freed free(header); }
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.