2

I am using a 3rd-party static library implemented in C++. I'm wrapping that object in a custom ObjC object in h and mm files, basically following the method described here. Many of the functions in the library return C-style arrays of ints or doubles. So, for example, in thirdParty.h / .cpp:

const double * getCoefficients()

My wrapper code in myThirdPartyWrapper.mm then looks something like:

@interface myThirdPartyWrapper ()
{
    thirdPartyObject *wrappedObj;
}

@end

@implementation myThirdPartyWrapper

- (id)init
{
    self = [super init];
    if (self)
    {
        // Create third-party object
        wrappedObj = new thirdPartyObject;
        if (!wrappedObj) self = nil;
    }

    return self;
}

- (void)dealloc
{
    delete wrappedObj;

    // Don't need [super dealloc] since we are using ARC
}

- (const double *)myGetCoefficients
{
    return wrappedObj->getCoefficients();
}

And, back in my .m file:

myThirdPartyWrapper *wrapper = [[myThirdPartyWrapper alloc] init];

const double *coeff = [wrapper myGetCoefficients];

// etc...

This seemed to work OK for me for functions returning a double * but crashed when returning a double **. Then I read that it is not safe to return C-style arrays in objective-C because the memory pointed to by the local variables in the function are freed by ARC, so (in my example) coeff would point to nothing. So instead I should do something like:

- (NSArray *)myGetCoefficients
{
    double *Carray = wrappedObj->getCoefficients();
    NSArray *array = [[NSArray alloc] init];
    for (int i = 0; i < numItems; i++)
        [array addObject:[NSNumber numberWithDouble:Carray[i]]];
    return array;
}

My questions:

  1. Is it a fluke that I'm not getting errors/crashes for functions that return double * or is this OK for some reason?

  2. The bigger question: Why does it even work to convert to NSArray? Why doesn't ARC free the memory created in wrappedObj->getCoefficients() so that Carray becomes unusable in myGetCoefficients?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
LarrySnyder610
  • 2,277
  • 12
  • 24
  • ARC does ***NOT*** free C and C++ raw arrays created using `malloc` or `new`. That's why the conversion to `NSArray` works. It does, however, deallocate unused **Objective-C** objects. In `dealloc`, you `delete` the C++ object, so its reference to the C-style primitive array will be invalid, and that's why it's crashing. It is **not** ARC itself that frees it. – The Paramagnetic Croissant Aug 24 '14 at 23:39
  • @TheParamagneticCroissant: OK, turns out I mis-read another SO post that made me think ARC cleans up the memory pointed to by the pointer returned by the function. But my code does not dispose of `wrapper` (doesn't call `dealloc`) before using the `double **` returned by the function. Any other thoughts about what might be causing memory problems? – LarrySnyder610 Aug 25 '14 at 00:15
  • Oops, never mind. I re-coded it using C-style arrays and it worked. Must have had a bug the first time around. Thanks. – LarrySnyder610 Aug 25 '14 at 00:30
  • 1
    ARC has the right to deallocate the object the very moment it's last used. So after you write `[wrapper myGetCoefficients];`, you don't presumably use `wrapper` anymore, so ARC frees it. (Side note: you never call `dealloc` manually. You dispose of ownership of an object using `release`, and then `dealloc` will be called automagically when needed.) – The Paramagnetic Croissant Aug 25 '14 at 00:33
  • OK thanks for the clarification on both of these points. If you want to make this an answer I'll vote it. – LarrySnyder610 Aug 25 '14 at 00:37
  • One more question -- all of this is only relevant if `thirdPartyObject` is returning a pointer to an array that's part of the class, right? If `getCoefficients()` creates a new array and returns that, there's no problem -- right? – LarrySnyder610 Aug 25 '14 at 00:42
  • 1
    If `getCoefficients()` creates a new array, then you have the problem of how to delete it when you're done with it. Either `thirdPartyObject` owns it or you do. If `thirdPartyObject` owns it, then the problem remains the same. If you do, then, well, it still remains the same because it will have to be held in an instance variable of `myThirdPartyWrapper` and deleted in its `-dealloc` method. – Ken Thomases Aug 25 '14 at 03:31
  • That's what I figured. Thanks. – LarrySnyder610 Aug 25 '14 at 11:31

1 Answers1

2

ARC can release wrapper immediately after the call to -myGetCoefficients if that's the last use. When it releases it, that may result in its deallocation, which would deallocate the C++ object and its coefficients array.

Annotate your -myGetCoefficients method with NS_RETURNS_INNER_POINTER:

- (const double *)myGetCoefficients NS_RETURNS_INNER_POINTER
{
    // ...
}

This tells ARC to keep the receiver around in an attempt to keep the returned pointer valid for longer. According to the docs, it will extend its life at least until:

  • the last use of the returned pointer, or any pointer derived from it, in the calling function or
  • the autorelease pool is restored to a previous state.
Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • E.g. if the pointer is used in a block created within "the calling function" but block itself is executed after calling function's return (say, asynchronously) then no guarantees are made. In that case you have to capture the object inside the block to extend its lifetime. – Kentzo May 25 '20 at 16:11