Ask Your Question

Revision history [back]

I encountered the same problem with the following environment:

  • Compiler: VC2013
  • OpenCV: 3.0(self build)
  • Build Configuration: Debug(x86)

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:

  • The allocator uses msvcr120d.dll!operator new() (Debug C Runtime of VC2012).
  • The deallocator uses MyApp.exe!operator delete() (replaced by the use of Heap debugging feature of VC2012 (crtdbg.h))

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.

I encountered the same problem with the following environment:

  • Compiler: VC2013
  • OpenCV: 3.0(self build)
  • Build Configuration: Debug(x86)

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:

  • The allocator uses msvcr120d.dll!operator new() (Debug C Runtime of VC2012).
  • The deallocator uses MyApp.exe!operator delete() (replaced by the use of Heap debugging feature of VC2012 (crtdbg.h))

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.