6

I am now confused by pointer to pointer even though I've read Why does NSError need double indirection? (pointer to a pointer) and NSError * vs NSError ** and much more.

I've done some thinking and still got some questions.

Here I wrote this:

NSError *error = [NSError errorWithDomain:@"before" code:0 userInfo:nil];
NSLog(@"outside error address: %p", &error];
[self doSomethingWithObj:nil error:&error];

In order to test the above NSError method, I wrote this:

- (id)doSomethingWithObj:(NSObject *)obj error:(NSError *__autoreleasing *)error
{
    NSLog(@"inside error address: %p", error);
    id object = obj;
    if (object != nil)
    {
        return object;
    }
    else
    {
        NSError *tmp = [NSError errorWithDomain:@"after" code:0 userInfo:nil];
        *error = tmp;
        return nil;
    }
}

But I found that the two logging addresses are different. Why is that?

2016-08-19 19:00:16.582 Test[4548:339654] outside error address: 0x7fff5b3e6a58
2016-08-19 19:00:16.583 Test[4548:339654] inside error address: 0x7fff5b3e6a50

Shouldn't they be the same since that was just a simple value copy? If they should be different, how can pointer to pointer end up pointing to the same NSError instance?

Community
  • 1
  • 1
bolizhou
  • 1,208
  • 1
  • 13
  • 31
  • 2
    The compiler must be doing something with the pointer to pointer, perhaps for dealing with ARC. In the latest version of Objective-C pointers got too much smarts, so what used to be a simple value copy may no longer be that simple. You may want to re-phrase this question in a way that has nothing to do with `NSError` - say, "Pointer to pointer changes when passed as method parameter", or something similar. – Sergey Kalinichenko Aug 19 '16 at 11:16
  • 1
    But NSError* __autoreleasing* is a pattern that is used everywhere and that is recognised and handled differently by the compiler. – gnasher729 Aug 19 '16 at 12:03

2 Answers2

4

The variable in the caller has type NSError*. The address has type NSError* *. The function expect NSError* __autoreleasing *. Therefore the compiler creates a hidden variable of type NSError* __autoreleasing, copies the NSError* into the hidden variable before the call, and copies it back after the call to get the semantics of __autoreleasing right.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Excellent answer! I really forgot there was something in the clang docs similar to that. Can you suggest something to read on the details? – MANIAK_dobrii Aug 19 '16 at 12:29
  • @gnasher729 I'm not sure if the reason is related to `__autoreleasing` since I tested it again: after I removed the keyword `__autoreleasing`, I got the same issue--different addresses for inside and outside pointers. – bolizhou Aug 22 '16 at 03:01
0

So, after initialisation on the first line, error is a pointer to an NSError object.

In the first log, you are logging the address at which that pointer is held. That's the effect of the & address-of operator. Anyway, that is the address gets passed into the doSomething method.

You're passing in: pointer -> pointer -> nserror-object.

But notice the double indirection in the signature of doSomething. The autoreleasing annotation makes it hard to spot, but its NSError **.

So the compiler takes your parameter and 'unwraps' it twice.

It starts as pointer -> pointer -> nserror-object. Then after the first indirection it becomes pointer -> nserror-object. Then after the second indirection it becomes nserror-object.

Ergo, you are logging two different things. The first is the address of the pointer to the nserror-object. The second is the address of the nserror-object itself.

EDIT: @MANIAK_dobrii points out that the object pointed to by error is itself different in the before and after case.

That's true. If an error occurs in doSomething then it creates an entirely new NSError instance in the else clause. It then loads that back into the error pointer. That's why you would see two different addresses, afterwards the error pointer is pointing to another object entirely.

ncke
  • 1,084
  • 9
  • 14
  • Not the case, error is already a pointer to NSError instance, &error is a pointer to that pointer. The thing is that before method call and after they somehow are different. I don't know why as well and I'd suppose they were the same. – MANIAK_dobrii Aug 19 '16 at 11:37
  • @MANIAK_dobrii Yes, I'm saying that 'error is already a pointer to NSError instance, &error is a pointer to that pointer'. But I think I see your point. Yes, they are different because, in the method, another NSError instance gets spun up in the else block and loaded into the pointer. It's a different object. – ncke Aug 19 '16 at 11:46
  • That's not the case either. @gnasher729 gave the exact correct answer. That's ARC messing around, I remember reading about that in some clang docs around __autoreleasing for double pointers, so give it a look. Also, sorry, I was not clear in "after (method call)", I meant after method call inside the call, i.e. during the call. – MANIAK_dobrii Aug 19 '16 at 12:32