1 | initial version |
I encountered the same problem with the following environment:
After some investigating, I found the cause of it finally. The root cause is the use of uncorresponding malloc/free and this is a design defect of cv::OutputArray.
Passing a std::vector as a cv::OutputArray, cv::OutputArray allocates a memory when the vector is empty, but the caller must deallocate the memory. In general, alloc/dealloc functions must be used correspondingly, so the current implementation of cv::OutputArray is essentially a bug which should be fixed.
Detailed Explanation
I identified malloc/free of a problematic allocation, then tracked the moments of allocation/deallocation.
Their callstacs are as follows:
Allocation
msvcr120d.dll!_heap_alloc_dbg_impl(unsigned int nSize, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) 行 486 C++
msvcr120d.dll!_nh_malloc_dbg_impl(unsigned int nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) 行 239 C++
msvcr120d.dll!_nh_malloc_dbg(unsigned int nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine) 行 302 C++
msvcr120d.dll!malloc(unsigned int nSize) 行 56 C++
msvcr120d.dll!operator new(unsigned int size) 行 59 C++
opencv_world300d.dll!std::_Allocate<cv::Vec<int,4> >(unsigned int _Count, cv::Vec<int,4> * __formal) 行 28 C++
opencv_world300d.dll!std::allocator<cv::Vec<int,4> >::allocate(unsigned int _Count) 行 578 C++
opencv_world300d.dll!std::_Wrap_alloc<std::allocator<cv::Vec<int,4> > >::allocate(unsigned int _Count) 行 848 C++
opencv_world300d.dll!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::_Reallocate(unsigned int _Count) 行 1588 C++
opencv_world300d.dll!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::_Reserve(unsigned int _Count) 行 1619 C++
opencv_world300d.dll!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::resize(unsigned int _Newsize) 行 1107 C++
opencv_world300d.dll!cv::_OutputArray::create(int d, const int * sizes, int mtype, int i, bool allowTransposed, int fixedDepthMask) 行 2343 C++
opencv_world300d.dll!cv::_OutputArray::create(int _rows, int _cols, int mtype, int i, bool allowTransposed, int fixedDepthMask) 行 2206 C++
opencv_world300d.dll!cv::findContours(const cv::_InputOutputArray & _image, const cv::_OutputArray & _contours, const cv::_OutputArray & _hierarchy, int mode, int method, cv::Point_<int> offset) 行 1743 C++
Deallocation
MyApp.exe!free_dbg_nolock(void * const block, const int block_use) 行 892 C++
MyApp.exe!_free_dbg(void * block, int block_use) 行 1011 C++
MyApp.exe!operator delete(void * block) 行 17 C++
MyApp.exe!std::_Deallocate(void * _Ptr, unsigned int _Count, unsigned int _Sz) 行 138 C++
MyApp.exe!std::allocator<cv::Vec<int,4> >::deallocate(cv::Vec<int,4> * _Ptr, unsigned int _Count) 行 638 C++
MyApp.exe!std::_Wrap_alloc<std::allocator<cv::Vec<int,4> > >::deallocate(cv::Vec<int,4> * _Ptr, unsigned int _Count) 行 910 C++
MyApp.exe!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::_Tidy() 行 1662 C++
MyApp.exe!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::~vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >() 行 976 C++
From the callstacks, it turns out that:
Actually, its internal debug heap information is interpreted differently:
Allocation
pHead 0x00eb7d18 {pBlockHeaderNext=0x00e42768 {pBlockHeaderNext=0x00e426f8 {pBlockHeaderNext=0x0056d8d0 {pBlockHeaderNext=...} ...} ...} ...} _CrtMemBlockHeader *
pBlockHeaderNext 0x00e42768 {pBlockHeaderNext=0x00e426f8 {pBlockHeaderNext=0x0056d8d0 {pBlockHeaderNext=0x0054a088 {pBlockHeaderNext=...} ...} ...} ...} _CrtMemBlockHeader *
pBlockHeaderPrev 0x00000000 <NULL> _CrtMemBlockHeader *
szFileName 0x00000000 <NULL> char *
nLine 0 int
nDataSize 608 unsigned int
nBlockUse 1 int
lRequest 1056 long
&(pHead)->nDataSize 0x00eb7d28 {608} unsigned int *
&(pHead)->nBlockUse 0x00eb7d2c {1} int *
Deallocation
header 0x00eb7d18 {_block_header_next=0x00e42768 {_block_header_next=0x00e426f8 {_block_header_next=0x0056d8d0 {...} ...} ...} ...} _CrtMemBlockHeader * const
_block_header_next 0x00e42768 {_block_header_next=0x00e426f8 {_block_header_next=0x0056d8d0 {_block_header_next=0x0054a088 {...} ...} ...} ...} _CrtMemBlockHeader *
_block_header_prev 0x00e95a60 {_block_header_next=0x00eb7d18 {_block_header_next=0x00e42768 {_block_header_next=0x00e426f8 {...} ...} ...} ...} _CrtMemBlockHeader *
_file_name 0x00000000 <NULL> const char *
_line_number 0 int
_block_use 608 int
_data_size 1 unsigned int
_request_number 1056 long
&(header)->_block_use 0x00eb7d28 {608} int *
&(header)->_data_size 0x00eb7d2c {1} unsigned int *
From the above, it turns out that the member nDataSize and nBlockUse are flipped.
2 | No.2 Revision |
I encountered the same problem with the following environment:
After some investigating, I found the cause of it finally. The root cause is the use of uncorresponding malloc/free and this is a design defect of cv::OutputArray.
Passing a std::vector as a cv::OutputArray, cv::OutputArray allocates a memory when the vector is empty, but the caller must deallocate the memory. In general, alloc/dealloc functions must be used correspondingly, so the current implementation of cv::OutputArray is essentially a bug which should be fixed.
Detailed Explanation
In a debug session, I identified malloc/free the moments of allocation/deallocation of a problematic allocation, then tracked the moments of allocation/deallocation.memory block.
Their callstacs callstacks are as follows:
Allocation
msvcr120d.dll!_heap_alloc_dbg_impl(unsigned int nSize, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) 行 486 C++
msvcr120d.dll!_nh_malloc_dbg_impl(unsigned int nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) 行 239 C++
msvcr120d.dll!_nh_malloc_dbg(unsigned int nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine) 行 302 C++
msvcr120d.dll!malloc(unsigned int nSize) 行 56 C++
msvcr120d.dll!operator new(unsigned int size) 行 59 C++
opencv_world300d.dll!std::_Allocate<cv::Vec<int,4> >(unsigned int _Count, cv::Vec<int,4> * __formal) 行 28 C++
opencv_world300d.dll!std::allocator<cv::Vec<int,4> >::allocate(unsigned int _Count) 行 578 C++
opencv_world300d.dll!std::_Wrap_alloc<std::allocator<cv::Vec<int,4> > >::allocate(unsigned int _Count) 行 848 C++
opencv_world300d.dll!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::_Reallocate(unsigned int _Count) 行 1588 C++
opencv_world300d.dll!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::_Reserve(unsigned int _Count) 行 1619 C++
opencv_world300d.dll!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::resize(unsigned int _Newsize) 行 1107 C++
opencv_world300d.dll!cv::_OutputArray::create(int d, const int * sizes, int mtype, int i, bool allowTransposed, int fixedDepthMask) 行 2343 C++
opencv_world300d.dll!cv::_OutputArray::create(int _rows, int _cols, int mtype, int i, bool allowTransposed, int fixedDepthMask) 行 2206 C++
opencv_world300d.dll!cv::findContours(const cv::_InputOutputArray & _image, const cv::_OutputArray & _contours, const cv::_OutputArray & _hierarchy, int mode, int method, cv::Point_<int> offset) 行 1743 C++
Deallocation
MyApp.exe!free_dbg_nolock(void * const block, const int block_use) 行 892 C++
MyApp.exe!_free_dbg(void * block, int block_use) 行 1011 C++
MyApp.exe!operator delete(void * block) 行 17 C++
MyApp.exe!std::_Deallocate(void * _Ptr, unsigned int _Count, unsigned int _Sz) 行 138 C++
MyApp.exe!std::allocator<cv::Vec<int,4> >::deallocate(cv::Vec<int,4> * _Ptr, unsigned int _Count) 行 638 C++
MyApp.exe!std::_Wrap_alloc<std::allocator<cv::Vec<int,4> > >::deallocate(cv::Vec<int,4> * _Ptr, unsigned int _Count) 行 910 C++
MyApp.exe!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::_Tidy() 行 1662 C++
MyApp.exe!std::vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >::~vector<cv::Vec<int,4>,std::allocator<cv::Vec<int,4> > >() 行 976 C++
From the callstacks, it turns out that:
This alloc/dealloc mismatch leads a heap corruption.
Actually, its in my environment, it leads a misinterpreting of internal debug heap information is interpreted differently:due to the difference of _CrtMemBlockHeader runtime layout.
Allocation
pHead 0x00eb7d18 {pBlockHeaderNext=0x00e42768 {pBlockHeaderNext=0x00e426f8 {pBlockHeaderNext=0x0056d8d0 {pBlockHeaderNext=...} ...} ...} ...} _CrtMemBlockHeader *
pBlockHeaderNext 0x00e42768 {pBlockHeaderNext=0x00e426f8 {pBlockHeaderNext=0x0056d8d0 {pBlockHeaderNext=0x0054a088 {pBlockHeaderNext=...} ...} ...} ...} _CrtMemBlockHeader *
pBlockHeaderPrev 0x00000000 <NULL> _CrtMemBlockHeader *
szFileName 0x00000000 <NULL> char *
nLine 0 int
nDataSize 608 unsigned int
nBlockUse 1 int
lRequest 1056 long
&(pHead)->nDataSize 0x00eb7d28 {608} unsigned int *
&(pHead)->nBlockUse 0x00eb7d2c {1} int *
Deallocation
header 0x00eb7d18 {_block_header_next=0x00e42768 {_block_header_next=0x00e426f8 {_block_header_next=0x0056d8d0 {...} ...} ...} ...} _CrtMemBlockHeader * const
_block_header_next 0x00e42768 {_block_header_next=0x00e426f8 {_block_header_next=0x0056d8d0 {_block_header_next=0x0054a088 {...} ...} ...} ...} _CrtMemBlockHeader *
_block_header_prev 0x00e95a60 {_block_header_next=0x00eb7d18 {_block_header_next=0x00e42768 {_block_header_next=0x00e426f8 {...} ...} ...} ...} _CrtMemBlockHeader *
_file_name 0x00000000 <NULL> const char *
_line_number 0 int
_block_use 608 int
_data_size 1 unsigned int
_request_number 1056 long
&(header)->_block_use 0x00eb7d28 {608} int *
&(header)->_data_size 0x00eb7d2c {1} unsigned int *
From the above, it turns out that the member nDataSize and nBlockUse are flipped.