0

I have a template class called OrdinalObjectList which is simply a map with a key of int and object pointers. It's purpose is to provide an collection of object pointers that can be accessed by an ordinal key. Here is the class:

template <typename O>
class OrdinalObjectList {

public:
    std::map<int, O*> List;
    OrdinalObjectList() {};
    virtual ~OrdinalObjectList() 
    {
        // Need to delete the objects in the map
        typename std::map<int, O*>::iterator i;
        for (i = List.begin(); i != List.end(); i++)
        {
            O* d = i->second;
            delete d;
        }
    };

On destruction of the OrdinalObjectList, the destructor loops through the map and deletes the objects. This has worked fine up until now, however it is currently receiving a EXC_BAD_ACCESS error when deleting the second of two objects in the collection.

On the first pass d is 'FSCE::Customer' * 0x10088e600 which delete's without issue. On the second pass, d is 'FSCE::Customer' * 0x100897e00 which, when delete'd causes the EXC_BAD_ACCESS. I can access the members of the second 'd' in the debugger. i.e. d->lifeid int 2, indicating that the FSCE::Customer object is a valid object and that 'd' is a valid pointer.

What steps should I take next to track down the cause of the EXC_BAD_ACCESS?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Ian Thompson
  • 644
  • 1
  • 6
  • 11
  • 2
    Run the code using a tool intended to detect access after free, like `valgrind`. Most likely, a previous malloc/free/new/delete operation is corrupting the heap, causing this delete to fail. – David Schwartz Jan 05 '12 at 00:18
  • I'm doing that now. Valgrind for OSX is not complete, but I will see what it has to report. I should also note that the error does not occur if I run the program single-threaded, it only occurs after multi-threaded processing. I am wondering if the processing threads have not detached correctly and are somehow 'locking' the object. – Ian Thompson Jan 05 '12 at 01:18

3 Answers3

0

I can't tell for sure, but it is it possible your deleting the 1st and (nonexistent) 2nd items, rather than the 0th and 1st items? Make sure you're deleting what you think you're deleting.

mgold
  • 6,189
  • 4
  • 39
  • 41
  • No, it is deleting the correct items. I've just noticed that if I reduce the processing threads (the above code is run in the main thread) to 1, I don't get the error, and the correct objects are deleted without issue. – Ian Thompson Jan 05 '12 at 01:21
0

Edit: Below is incorrect.

Not really an answer, however when I reduced the number of threads to 4, the problem goes away. Previously I had set the number of threads to 8, the boxen is Core i7, which is 4 cores with Hyper Threading.

I can only assume that there is a problem with Hyper Threading, either in the OSX kernel or LLVM. I have optimisations set to O3, at some point I will turn off optimisations and see if that works on 8 threads, however in the meantime, 4 threads is only 10% slower than 8, so I will stick with that so I can progress.

The problem was with a large array within the objects I was deleting. The array was created in the constructor and deleted in the destructor similar to this (data member names have been changed):

Matrix::Matrix(int maxa, int maxb, int maxc) 
{
  asize = maxa;
  bsize = maxb; 
  csize = maxc;
  matrixsize = a * b * c;
  matrix = new double [matrixsize];
}
Matrix::~Matrix()
{
  delete [] matrix;
}

So far so good, however in setting the values in the matrix I had a bug.

void Matrix::SetValue(int a,int b,int c,double value)
{
  int index = (a * asize) + (b * bsize) + c;
  matrix[index] = value;
}

The bug in another part of the code that set 'maxc' meant that sometimes index would be greater than matrixsize, which I discovered by adding a check and throw.

void Matrix::SetValue(int a,int b,int c,double value)
{
  int index = (a * asize) + (b * bsize) + c;
  if (index >= matrixsize) throw;
  matrix[index] = value;
}

This would result in access to memory outside that which was allocated in the constructor, and when the delete was called, the EXC_BAD_ACCESS error being raised. The curious thing is why the EXC_BAD_ACCESS was not raised in Matrix::SetValue during execution, but I guess the answer has something to do with there being no bounds checking on array index offsets against the heap manager's memory bounds. If anyone can shed light on that I would be most interested, but for now, this answer is for anyone who finds this answer from a web search.

Ian Thompson
  • 644
  • 1
  • 6
  • 11
  • There's no index checking by the language. Operating system level memory protection also works only on MMU page boundaries, e.g. it's only triggered when trying to access a MMU page not allocated to you. What probably happened is that you wrote over the heap cell metadata usually residing just before the allocated heap block, depending on implementation. So when freeing a block with corrupt metadata, it resulted in the bad access. – laalto Jan 09 '12 at 11:21
  • Also note that your index calculation is probably not what you intended - e.g. for 3x3x3 matrix, both (1,2,3) and (2,1,3) share the same index. – laalto Jan 09 '12 at 11:27
  • Sorry, I typed that from memory, its actually: (a * bsize * csize) + (b * csize) + c; . Thanks for your comment on the MMU page boundaries though. – Ian Thompson Jan 10 '12 at 09:10
0

EXC_BAD_ACCESS can easily be traced by enabling zombie objects.

For XCode 4.x see How do I set up NSZombieEnabled in Xcode 4?

For other versions of XCode you can find it on the internet.

Community
  • 1
  • 1
Abdullah Umer
  • 4,234
  • 5
  • 36
  • 65
  • I thought NSZombieEnabled was only for Objective-C, I didn't realize it could be enabled for c++. Looking at this: http://www.cocoabuilder.com/archive/cocoa/226062-instance-variables-not-destructed-if-nszombieenabled.html It might be better to use guarded malloc. Thanks for your reply. – Ian Thompson Jan 06 '12 at 00:21