PDA

View Full Version : Memory headscratcher in a Calculator




GFLPraxis
May 11, 2011, 08:23 PM
So I built a calculator app in the process of learning Objective-C and Cocoa Touch, but I'm having a strange issue. The user can input an expression including variables and then the calculator evaluates that expression when they hit the Solve button. I've yet to build the mechanism for the user to input their own variables, so right now I set them to some arbitrary numbers.


This is called directly from an IBOutlet if the sender is the Solve button.

- (void)solve
{
NSMutableDictionary *variableValues = [[NSMutableDictionary alloc] init];

[variableValues setObject:[NSNumber numberWithDouble:2] forKey:[NSString stringWithFormat:@"x"]];
[variableValues setObject:[NSNumber numberWithDouble:3] forKey:[NSString stringWithFormat:@"y"]];
[variableValues setObject:[NSNumber numberWithDouble:4] forKey:[NSString stringWithFormat:@"a"]];
[variableValues setObject:[NSNumber numberWithDouble:5] forKey:[NSString stringWithFormat:@"b"]];

[display setText:[NSString stringWithFormat:@"%g",[CalculatorBrain evaluateExpression:brain.expression usingVariableValues:variableValues]]];


[variableValues autorelease];

return;

}
Here's the offending bit of code:
+ (double)evaluateExpression:(NSArray *)expression usingVariableValues:(NSDictionary *)variables
{

CalculatorBrain *brain = [[CalculatorBrain alloc] init];

for (id object in expression) {
if ([object isKindOfClass:[NSNumber class]]) {
brain.operand = [object doubleValue];
NSLog(@"%f",brain.operand);
} else if ([object isKindOfClass:[NSString class]]) {
if ([CalculatorBrain isVariable:object]) {
brain.operand = [[variables objectForKey:object] doubleValue];
NSLog(@"%f",brain.operand);
} else {
NSLog(@"%@",object);
[brain performOperation:object];
}
}
}


[brain performOperation:[NSString stringWithFormat:@"="]];


double result = brain.operand;

[brain autorelease];

return result;
}


If I comment out the second to last line, [brain autorelease];, the program works fine.

But this would result in a memory leak, since a new "brain" object is created every time the user presses Solve, so I would prefer not to do this.

brain.operator and result are both C primitives (doubles), not pointers, so it shouldn't be released when brain.operand is dealloc'd along with the rest of the brain object, correct? double result = brain.operator should create a duplicate?

So why does the program crash after I autorelease?



chown33
May 11, 2011, 08:57 PM
double result = brain.operator should create a duplicate?

That doesn't make sense. A double type is not an object, so it's never released.


So why does the program crash after I autorelease?

You haven't posted enough code to answer. The cause could be anywhere in the code you haven't posted. Just because it crashes in the posted code doesn't mean that's where the cause of the problem lies.

I suggest that you keep the autorelease in place, then run your program under Instruments with zombies enabled.
http://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/AboutTracing/AboutTracing.html

If there's an over-release or under-retain, then running with zombies should identify it.

Another suggestion: do a Build and Analyze. If it points out any potential memory errors, you should look at them very closely.

robbieduncan
May 12, 2011, 04:23 AM
Why are you using autorelease? That is intended to be used when you are going to return an object from your method and want to disown it before returning it. You should just be calling release.

GFLPraxis
May 12, 2011, 11:28 AM
Why are you using autorelease? That is intended to be used when you are going to return an object from your method and want to disown it before returning it. You should just be calling release.

It's a leftover of how I wrote it originally; good point, I've changed it to release.

That doesn't make sense. A double type is not an object, so it's never released.

Perhaps I need some clarification on this. Are primitives always leaked then? I assumed brain.operand (being an instance variable) would go away when brain was dealloc'd.



You haven't posted enough code to answer. The cause could be anywhere in the code you haven't posted. Just because it crashes in the posted code doesn't mean that's where the cause of the problem lies.

I was about to argue that I'd posted the entire life cycle of the "brain" object, when I realized I hadn't actually checked my dealloc function (which actually only released two objects). Then I realized that I was overreleasing a MutableString that I hadn't retained but rather passed in to an array that I also dealloc'd. Whups. Got it! Thanks for the suggestions, I'll play around with zombies.

robbieduncan
May 12, 2011, 11:35 AM
Perhaps I need some clarification on this. Are primitives always leaked then? I assumed brain.operand (an instance variable) would go away when brain's dealloc was called.

Normally primatives are never leaked. Normally because they are allocated on the stack rather than on the heap. Your double that you store the value of brain.operand is certainly on the stack. Not that this has nothing to do with brain.operand being an instance variable. You copy the value of brain.operand into result. There is no coupling of one to the other. There are no pointers here.

Perhaps I am assuming to much here but I'd say you've learnt Objective-C without learning C first. You really need to step back, learn C and start to understand the difference between the stack and the heap and between a "normal" variable and a pointer.

chown33
May 12, 2011, 01:05 PM
I was about to argue that I'd posted the entire life cycle of the "brain" object, when I realized I hadn't actually checked my dealloc function (which actually only released two objects). Then I realized that I was overreleasing a MutableString that I hadn't retained but rather passed in to an array that I also dealloc'd. Whups. Got it! Thanks for the suggestions, I'll play around with zombies.

I suggest putting the bug back into place, then doing two things:
1. Perform a Build and Analyze and see if it finds any problems.
2. Make a few runs with zombies enabled.

I'm suggesting this because you already know what the results should be, you're simply learning how to use the tools to point at the known results. Believe me, this is a lot easier than having to learn these tools while actually looking for an unknown bug.

If both tools (Build and Analyze, and zombies) point at the same thing, then fix the bug and run the tools again. Once again, you know what the results should be.

If at any point, the tools don't work properly, such as not pointing to the known bug, or not ceasing to point at a removed bug, then post again with the results at that point. Getting the wrong results may mean tool misuse, or misconfiguration, or some other problem.

GFLPraxis
May 12, 2011, 01:32 PM
Normally primatives are never leaked. Normally because they are allocated on the stack rather than on the heap. Your double that you store the value of brain.operand is certainly on the stack. Not that this has nothing to do with brain.operand being an instance variable. You copy the value of brain.operand into result. There is no coupling of one to the other. There are no pointers here.

Perhaps I am assuming to much here but I'd say you've learnt Objective-C without learning C first. You really need to step back, learn C and start to understand the difference between the stack and the heap and between a "normal" variable and a pointer.

I'm...rusty in C. I've got more experience in Java, and I took a C class in college- somewhere around five years ago- and haven't used it since, so I've been remembering things I'd forgotten as I run in to them. This was one of those cases (and yes, I do recognize the difference between the stack and heap).

Thanks :)