Re: string pointer


Quincey Morris
 

On Mar 1, 2018, at 04:13 , Gerriet M. Denkmann <g@...> wrote:

But my real problem is this:

BOOL needReason = …
NSString *s;
NSString **stringPointer = needReason ? &s : NULL;
NSUInteger c = [ self computeFor: 42 reason: stringPointer ]; // sometimes want to know the reason

I cannot figure out how to declare the stringPointer without getting compiler warnings.

So, here’s what’s going on. It’s a bit messy.

— A pointer has an ownership attribute, which is __strong by default, but can be explicitly specified as __weak or __autoreleasing. In your code, variable “s” is __strong by default.

— A pointer to a pointer needs to know the ownership of the pointee, and there is no default (in most cases), so you get the error "Pointer to non-const type 'NSString *' with no explicit ownership” for your declaration of “stringPointer”.

— If you specify the actual ownership explicitly:

NSString *__strong*stringPointer = needReason ? &s : NULL;

that error goes away, but there is still an error on the call: “Passing address of non-local object to __autoreleasing parameter for write-back”.

That’s because a pointer to a pointer that’s a method parameter *does* have a default pointee ownership, “__autoreleasing”. (When you added the explicit “__autoreleasing”, you didn’t change anything that wasn’t already assumed.)

— So, you can declare your “computeFor:” method like this:

- (NSUInteger)computeFor: (NSUInteger)arg  reason: (NSString *__strong*)reason

and everything would be just great, and this is the safest route. The only irritation is that every "NSString**” you pass into “computeFor:reason:” is going to need to be explicitly annotated, including those that pass through other methods, so the need to annotate is going to ripple outwards across your code.

— This solution:

NSUInteger c = [self computeFor:42 reason:needReason ? &s : nil];

is AFAIK a bit dangerous. The “computeFor:reason:” method stores an *autoreleased* string pointer into its output parameter, so “s” will end up containing this autoreleased pointer when “needReason” is true. That autoreleased pointer is a ticking time bomb that will explode if it’s still being used somewhere when its autorelease pool is drained  (which could be a *lot* later, leading to hard-to-find bug) without being owned somewhere else. Whether this is a problem depends on what you do with “s” next.

FWIW, all APIs that have a "NSError**outError” parameter have this same danger. (Charles Srstka pointed this out a few months ago.) Our apps work because the unowned pointer usually gets thrown away fast, or gets stored somewhere that gives it an extra retain.

Join cocoa@apple-dev.groups.io to automatically receive all group messages.