Cannot understand NSError/NSObject pointer passing

2019-02-13 14:52发布

问题:

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?

回答1:

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.



回答2:

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.