Accessing a released NSString doesn't make the app crash - objective-c

I know this is probably the exact opposite of the questions asked on Stackoverflow, but something really weird happened to me today.
I was trying to show someone how to work with Instruments and NSZombies, so I tried to create a crash.
I declared a NSString, released it and then tried to access it but the app didn't crash
NSString* test = [[NSString alloc] init];
[test release];
NSlog(#"%#", test);
I even tried to release it twice, but it still didn't make the app crash, it just printed null.
Can anyone please explain me what I did wrong or where is the flaw in my logic?
Thank you
EDIT : I tried something like this too and still no crash
NSString* test = [[NSString alloc] init];
test = #"something";
NSlog(#"%#", test);
[test release];
NSlog(#"%#", test);
I even added two consecutive releases, and a test = nil; after a release, just to make sure.

NSString may sometimes behave strangely.
In your example, you allocated a NSString without data.
That's the reason why it doesn't crash.
Allocating a NSString without data has no sense, as NSString objects are immutable.
In such a case, NSString will return a kind of singleton instance, meaning you can't release it (well, you can, but it will have no effect).
Every time you allocate a NSString object this way, the same instance will be returned.
Try this instead:
NSString * test = [ [ NSString alloc ] initWithCString: "hello, world" encoding: NSUTF8StringEncoding ];
[ test release ];
NSLog( #"%#", test );
Then it will crash, as expected...
To prove what I have explained earlier, try this:
NSString * test1 = [ [ NSString alloc ] init ];
NSString * test2 = [ [ NSString alloc ] init ];
NSLog( #"OK: %p %p", test1, test2 );
You can see the same address is printed, meaning only one object is allocated (the "singleton" one, for NSString objects without data).
EDIT
I've seen on your comment that you tried to "give the string a value".
This is not possible using NSString, as they are immutable.
I guess you tried this:
NSString * s = [ [ NSString alloc ] init ];
s = #"hello, world";
In such a case, you are not giving a value!
You are re-assigning a pointer!
It means your s variable (which is a pointer) will then point to another string object.
And it also mean you won't be able to access your previously allocated string, as you just lost the pointer to it.
If you want string objects that can be changed, take a look at the NSMutableString class.
But keep in mind that changing a pointer value is not the same as changing an object's value!

There are two things going on in your example.
First, nothing else happened between your release and your NSLog that would modify the memory where your string was stored, so it happens to still be intact. You can't rely on that.
Second, the system has a special case for [[NSString alloc] init]. It returns a predefined object of type __NSCFConstantString that is never deallocated. If you print its reference count, you'll see that it is 2147483647 (0x7fffffff). That is a magic number indicating that any attempt to retain or release the object is ignored.

If you take a look at the retainCount on an NSString allocated with either #"some_string" or [[NSString alloc] init] then you'll see that it's -1. This is because the compiler has known what the object is at compile time and has managed to store it as a literal. So when you release it, it's not actually going to do anything. It's not an object that has been allocated at runtime - it's just always there in your application binary.
You could make your crash occur by doing something like this on Mac:
#import <Foundation/Foundation.h>
int main(int argc, char** argv) {
NSString *test = [[NSString alloc] initWithCString:argv[0] encoding:NSASCIIStringEncoding];
NSLog(#"test = %p", test);
[test release];
NSLog(#"test = %p", test);
[test release];
NSLog(#"test = %p", test);
return 0;
}
The string is now no longer known at compile time, so it will actually be an object allocated and have a positive retain count, etc.
Note that NSNumber (on Mac OS X Lion) also exhibits this behaviour if the number is known at compile time. This is even more interesting as it's an example of tagged pointers in action. Take a look at this blog article for a discussion on that. And here is some code to show it:
#import <Foundation/Foundation.h>
int main(int argc, char** argv) {
NSNumber *test = [[NSNumber alloc] initWithInt:1];
NSLog(#"test = %p", test);
[test release];
NSLog(#"test = %p", test);
[test release];
NSLog(#"test = %p", test);
return 0;
}
Note that also doesn't crash, it just prints out test = 0x183 3 times.

Related

Retain and release nsstring

str is a NSString I own. It is my responsibility to release it,
since I call initWithString:
if (![[str substringFromIndex:str.length-1] isEqualToString:#"\n"])
{
str = [str stringByAppendingString:#"\n"];
}
if the line inside the if statement reached, I will lose the ownership of the str var.
so my app crashes with a zombie instance, when I release str later:
[str release];
All goes fine if the if statement is NO(false).
What can I do to maintain the ownership of str?
Note that str could be very long I don't want to init another NSString
You need to do normal memory management:
if (![[str substringFromIndex:str.length-1] isEqualToString:#"\n"])
{
NSString *newStr = [str stringByAppendingString:#"\n"];
[str release];
str = [newStr retain];
}
Keep in mind that stringByAppendingString: returns an autoreleased string (and it also creates a whole new string).
Suggestion 1: Use ARC. It solves these problems for you. This is by far the best solution.
As rmaddy says, Xcode has an automated tool for converting apps to ARC. Look in the edit menu, under refactor>Convert to Objective-C ARC. The process is fairly painless. It flags things it wasn't able to figure out on it's own (usually only a few things.) After you clean up those issues you are off and running and never have to worry about retain counts again.
Suggestion 1a: Make str a mutable string, as #rmaddy suggested.
Then your code would look like this:
[str appendString: #"\n"];
That's simpler, easier to read, more memory-efficient, and works exactly the same in both ARC and manual reference counting.
Failing that, change str to be a retained property
#property (nonatomic, retain) NSString *str);
Then use property notation:
if (![[self.str substringFromIndex: self.str.length-1] isEqualToString:#"\n"])
{
self.str = [self.str stringByAppendingString:#"\n"];
}
When you do that the setter for the property takes care of releasing the old object in the str property before assigning a new value to the property.
Be aware, though, that assigning an object to a retained property increases it's retain count. This will create a leak:
self.str = [NSString alloc] initWithFormat: #"number %d", value];
(because all alloc/init calls return objects with a retain-count of 1)
and then the property retains it again.
That code should be written like this:
self.str = [[NSString alloc] initWithFormat: #"number %d", value] autorelease];

Objective-C Changing values of instance variables in method

I am write a Objective-C Code on XCode 4.4.
I have a NSMutableArray as a instance variable of my class k_info
I have defined and synthesized (nonatomic,retain) property by the name of onesplaces too.
I am unable to add a NSMUtableString object in the NSMutableArray onesplaces.
When I try to add it.The size of onesplaces remains 0 and object at zero index obviously remains null.
I tried doing this with and without using "self" key-word but it didnt worked in either case.
My syntax of adding object and printing it is right because when I create a new NSMutableArray test
and try to do the same thing it works for it but not for the instance variable onesplaces.
I cannot proceed any further in my project without solving this issue.please tell me why is it
happening and how should I solve this problem.
-(void)createinfo:(NSMutableArray )al varsis:(int)vars
{
NSMutableString stes=[[NSMutableString alloc]init];
stes=(NSMutableString*)#"string0";
[ onesplaces addObject:stes];
NSLog(#"%u",[onesplaces count]);
NSLog(#"value is: %# ",[ onesplaces objectAtIndex:0]);
[ self.onesplaces addObject:stes];
NSLog(#"%u",[onesplaces count]);
NSLog(#"value is: %# ",[self.onesplaces objectAtIndex:0]);
NSMutableArray* test=[[NSMutableArray alloc]init];
[ test addObject:stes];
NSLog(#"%u",[test count]);
NSLog(#"value is: %# ",[test objectAtIndex:0]);
}
You probably forgot to create the array. Somewhere in your code, maybe in your init method, you need to create the array before using it.
self.onesplaces = [[NSMutableArray alloc] init];
You get nil instead of error messages because Objective-C allows you to send messages to nil, which always return nil.

Allocation and release problem

I'm trying to explain memory management on iOS to a friend and I'm showing him a wrong code. But when I'm launching the app, it's working and I don't know why.
Here's the snippet :
NSString *myString = [[NSString alloc] initWithString:#"myString"];
[myString release];
NSLog(#"%#",myString);
I don't understand why my NSLog is working ...
Do you have some explanations ?
Thanks !
There are two things to bear in mind with regard to your example.
As MByD explained, accessing an object that’s been deallocated is undefined behaviour. It might or might not crash your program — it depends on whether the memory that had been allocated to that object has been reused and what’s been put there. In some cases the memory hasn’t been reused yet (and you might think the object is still alive but that object is in fact a ghost object), in other cases the memory may have been reused by another Objective-C object (the program won’t crash but you’ll see a different object) and in other cases the memory may have been reused by something that’s not an Objective-C object (in which case the program will likely — but not necessarily — crash).
Your string object is a constant string. As explained in the answer to this question and its comments, a constant string is never deallocated. When you send -[NSString initWithString:] passing a constant string as the argument, Cocoa returns the original constant string so your code is effectively the same as NSString *myString = #"myString";. This is an internal Cocoa implementation detail though. Production code should always consider that objects returned by +alloc (and, usually, subsequent -init) are owned by the caller, should be released when the caller is not interested in them anymore, and won’t be available after deallocation.
For the sake of experimentation, try the following code:
#import <Foundation/Foundation.h>
#include <stdio.h>
int main(void) {
[NSAutoreleasePool new];
NSString *s1 = [[NSString alloc] initWithString:#"myString"];
NSString *s2 = [[NSString alloc] initWithString:#"myString"];
NSString *s3 = [[NSString alloc] initWithString:#"myString"];
NSString *s4 = [[NSString alloc] initWithString:#"myString"];
printf("s1 = %p\n", s1);
printf("s2 = %p\n", s2);
printf("s3 = %p\n", s3);
printf("s4 = %p\n", s4);
[s1 release];
[s2 release];
[s3 release];
[s4 release];
return 0;
}
Conceptually, s1, s2, s3, s4 should be different objects. However, by running this program you can see that they are effectively the same object (they have the same address):
$ ./a.out
s1 = 0x10080b090
s2 = 0x10080b090
s3 = 0x10080b090
s4 = 0x10080b090
This is an undefined behavior. You are not allowed to access this string, yet it might be available.

autoreleasing NSString in class method causing app crash in iOS

The error I receive is as follows:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil); //breakpoint that says Thread 1: Program Received Signal: "EXC_BAD_ACCESS".
[pool release];
return retVal;
}
My two questions can be found at the bottom of this post :)
I am currently working on an assignment for an iOS programming class and have hit a road bump.
I have found a fix, shown below, but it doesn't make sense to me. Check it out:
#implementation MyClass
// This class method takes an (NSMutableArray *) and returns an NSString with its contents printed out.
+ (NSString *)myString:(NSMutableArray)anArray
{
// NSString *myString = [[NSString alloc] init]; OLD CODE THAT CAUSES MEMORY LEAK
NSString *myString = [[[NSString alloc] init] autorelease]; //NEW CODE THAT RELEASES FIRST ALLOCATION OF myString WHEN THE FIRST stringByAppendingFormat: IS CALLED
NSString *vp = VARIABLE_PREFIX; //#defined above to be #"%
for (id object in anArray) {
if ([object isKindOfClass:[NSString class]]) {
if ([object hasPrefix:vp]) {
myString = [myString stringByAppendingFormat:#"%#",[object substringFromIndex:1]];
}else{
myString = [myString stringByAppendingFormat:#"%#",object];
}
}else if ([object isKindOfClass:[NSNumber class]]) {
myString = [myString stringByAppendingFormat:#"%#",object];
}
}
return myString; //shouldn't I autorelease myString right before this line? NO NOT ANY MORE. THIS myString IS NOT THE ORIGINAL THAT I alloc-init, BUT AN AUTORELEASED OBJECT RETURNED BY THE stringByAppendingFormat: message.
}
When I try to send the message [myString autorelease];, the program crashes with the above error. It is working fine now as shown above, but I do not understand why.
Every time I send a message containing the "magic words" alloc, init, copy I have to call release, it don't I? Or are the rules different in a Class method (can the Class itself own a file?). I do not call retain in the object that is calling this file.
Here are my two questions:
Why does this crash when I try to release theDescription using autorelease?
Does my code create a memory leak?
This is my very first question on stack overflow! Thank you for your help!
Why does this crash when I try to release theDescription using autorelease?
Assuming you mean myString, it crashes because myString is already autoreleased. You got it by calling -stringByAppendingFormat:, which returns an autoreleased string. Now, you're probably thinking: "But I created it by calling +alloc, so I should release it." That's true, but NSStrings are immutable, and when you call -stringByAppendingFormat: you get a different string back, and that string is autoreleased. Autoreleasing it a second time is an error.
Does my code create a memory leak?
Yes, but not really. The "leaked" object is the empty string that you allocate in the beginning. You never release that string, so you've got a leak. However, NSString is apparently optimized so that [[NSString alloc] init] returns a singleton, so in this particular case it doesn't make any difference that the empty string isn't released. The other strings that are assigned to myString are all autoreleased, so none of those objects are leaked.
Why does this crash when I try to release theDescription using
With the updated code, the problem is that ypu are reassigning the pointer to myString using the methid which already returns an autoreleases object: stringbyappending, therefore if you call autorelease on this object which is already going to get autoreleased i will crash.
Aditionaly the first assugnment in the alloc init gives a memory leak when ypu reassign with stringbyappendingstring, since you lose the reference to the previously created string with alloc init and therefore you will never be able to release it.
Sorry for the formatting on my iPhone atm =)

Memory Question

I have some code that results in a EXC_BAD_ACCESS error:
recordIDAsString = [
NSString stringWithFormat:#"%i", (int)abRecord.recordID
];
propertyIDAsString = [
NSString stringWithFormat:#"%i", (int)abProperty.propertyID
];
identifierAsString = [
NSString stringWithFormat:#"%i", (int)abProperty.identifier
];
recordIDAsString, propertyIDAsString, and identifierAsString are all defined in the interface. The code is contained in an editing view controller, and the three *AsString variables seem to work fine until the save button is pressed, when their values become invalid. However, I've discovered that the following code does work:
NSString *tempRecordIDAsString = [
NSString stringWithFormat:#"%i", (int)abRecord.recordID
];
NSString *tempPropertyIDAsString = [
NSString stringWithFormat:#"%i", (int)abProperty.propertyID
];
NSString *tempIdentifierAsString = [
NSString stringWithFormat:#"%i", (int)abProperty.identifier
];
recordIDAsString = [tempRecordIDAsString copy];
propertyIDAsString = [tempPropertyIDAsString copy];
identifierAsString = [tempIdentifierAsString copy];
I thought I only had to manage memory when I call alloc or init, so my question is: what is happening in the first code example that causes the memory to become invalid?
A string created via...
[NSString stringWithFormat:#"%i", (int)abRecord.recordID]
... is autoreleased. It will go away at the next autorelease pool -drain unless you -retain it.
You need to revisit the memory management guide:
http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
NSString stringWithFormat is a convenience constructor - meaning that it will the returned object is autoreleased so that you don't have to do that. The issue that causes, is that if you want to keep that value, you must retain or copy it. So even though you are storing a reference to the value, you are not incrementing its retainCount. So when the current run loop is complete, the autorelease pool will send a release to all its objects decreasing the retainCount by 1. If you use a retain, you will increment the retainCount by one, so that when autorelease releases the object, it will still have a retainCount until another release is sent - which should be done by you at some point - maybe in your dealloc.