PDA

View Full Version : Objective-C noob: EXC_BAD_ACCESS with simple print method?




dancks
Apr 25, 2013, 03:10 PM
I'm going through an exercise in a cocoa programming book, where there is a GUI and from a textfield I enter and to a list of strings displayed on a table. There are plenty of things that aren't working, the most confusing of which is I keep leaking data. First with NSMutableArray, so I decide to try a c style linked list for whatever reason. I got a prototype to work in C, apply it to my project, it leaks and seg faults whenever I try to to simply print out the string:

struct declaration in header:

struct que {
NSString *sentence;
struct que *next; };
typedef struct que que;
@interface linkedlist : NSObject
{
que *top;
int count;
}


method in question:


-(void)print_list
{
if(top==nil) {printf("In print_list, top lost its node!\n");exit(3);}
for(que *a=top;a!=0;a=a->next) NSLog(@"\n%@\n",a->next);
}


I even published/archived the app to be standalone and ran it with valgrind and it didn't detect any data loss until I pressed "Check List" button which calls print_list.

entire linkedlist.m (in case there's something I missed:


#import <stdlib.h>
#import <stdio.h>
#import "linkedlist.h"

@implementation linkedlist
-(id)init
{
[super init];
count = 0;
top = 0;
return self;
}
-(void)dealloc
{
[self destroy];
}
+(que *)get_new:(NSString *)str
{
que *nnew = 0;
if(!(nnew = malloc(sizeof(que)))) {printf("In get_new, malloc failed\n"); exit(1);}
nnew->sentence=str;
nnew->next=0;
if (nnew!=nil) printf("Good here\n");
return nnew;
}
+(void)pop:(que**)p
{
if(p!=0)
{
if((*p)!=0)
{
if((*p)->next==0)
{
printf("In pop, popping single node\n");
}
que *a = *p;
(*p)=(*p)->next;
a->next=0;
free(a);
}
else
{
NSLog(@"In pop: Pop passed a null pointer. going to crash\n");
que *a;
a->next=0; //crash
}
}
}
+(void)pop_in_middle:(que**)n
{
if(n!=0)
{
if((*n)!=0)
{
if(!(*n)->next)
{
printf("In pop_in_middle, que->next not set, not popping\n");
}
else
{
if(!(*n)->next->next)
{
printf("In pop_in_middle, que->next->next not set\n");
}
que *h1 = (*n)->next;
que *h2 = (*n)->next->next;
(*n)->next=h2;
h1->next=0;
free(h1);
}
}
}
}
+(void)pop_last:(que**)a
{
que *h = (*a)->next;
(*a)->next=0;
if(h->next)
{
NSLog(@"in pop_last, h->next is either dangling or points to valid memory. About to leak\n");
h->next=0;
}
free(h);
}
+(void)pushed:(que**)a pusher:(que*)b
{
b->next=(*a);
(*a)=b;
}
+(void)add_to_middle:(que**)a str:(NSString *)s
{
que *nnew = [linkedlist get_new:s];
que *holder = (*a)->next;
nnew->next=holder;
(*a)->next=nnew;
}


-(void)dropAt:(int) i
{
count = [self check_count];
if(top!=0)
{
if((!i)||(count==1))
{
NSLog(@"Popping top node\n");
[linkedlist pop:&top];
}
else if(i>=count)
{
NSLog(@"popping last node\n");
que *a;
for(a=top;a->next!=0;a=a->next);
[linkedlist pop_last:&a];
}
else if(count>=2)
{
NSLog(@"popping somewhere in the middle\n");
que *a = top;
for(int x=0;x<(i-1);x++) a=a->next;
[linkedlist pop_in_middle:&a];
}
else
{
NSLog(@"in dropAt, something is wrong with the logic\n");
}
count--;
}
else
{
NSLog(@"In dropAt: Top is 0\n");
}
}
-(void)place:(NSString*)str At:(int)i
{
count = [self check_count];
if(top==0)
{
NSLog(@"Top is initially null\n");
top = [linkedlist get_new:str];
if(top!=nil) printf("Good here apparently. Dereference:\n");
NSLog(@"\n%@\n",top->sentence);
}
else
{
que *nnew = 0;
que *a;
if(i<=0)
{
NSLog(@"Inserting que at top\n");
nnew = [linkedlist get_new:str];
[linkedlist pushed:&top pusher:nnew];
}
else if(i>=count)
{
NSLog(@"Inserting que at bottom\n");
nnew = [linkedlist get_new:str];
for(a=top;a->next!=0;a=a->next);
a->next=nnew;
}
else
{
NSLog(@"Inserting que in middle somewhere\n");
a=top;
for(int x=0;x<(i-1);x++) a=a->next;
[linkedlist add_to_middle:&a str:str];
}
}
count++;
}


-(void)print_list
{
if(top==nil) {printf("In print_list, top lost its node!\n");exit(3);}
for(que *a=top;a->next!=0;a=a->next) NSLog(@"\n%@\n",a->next);
}
-(int)check_count
{
int i=0;
for(que *a=top;a!=0;a=a->next) i++;
return i;
}
-(int)count
{
return count;
}
-(void)destroy
{
NSLog(@"Linked list about to be destroyed\n");
while(top!=0)
{
[linkedlist pop:&top];
}
}
-(NSString *)stringAt:(int)i
{
NSString *b = @"";
if(i<count)
{
que *a=top;
for(int z=0;((z<i)&&(a->next!=0));z++) a=a->next;
b=a->sentence;
}
return b;
}
@end



gnasher729
Apr 25, 2013, 03:30 PM
You used %@ which is the format specifier for NSObject*, but a->next is not an NSObject*. If you turn on warnings about format specifiers, the compiler will tell you.

dancks
Apr 25, 2013, 03:50 PM
You used %@ which is the format specifier for NSObject*, but a->next is not an NSObject*. If you turn on warnings about format specifiers, the compiler will tell you.

Ok. Would that explain the error message? Valgrind tells me it's try to access data of size 8. I assume that means an unallocated byte. Unless NSLog itself is causing the crash by using the wrong formatting, causing a default? What would I use in its place? Xcode warns me against %s, saying its the wrong data type

gnasher729
Apr 25, 2013, 04:04 PM
Ok. Would that explain the error message? Valgrind tells me it's try to access data of size 8. I assume that means an unallocated byte. Unless NSLog itself is causing the crash by using the wrong formatting, causing a default? What would I use in its place? Xcode warns me against %s, saying its the wrong data type

%@ for NSObject*.
%s for char*
%p to print a pointer.

a->next is neither an NSObject*, nor a char*, but a struct que*. So %p is the only one that would work.

Surely Xcode warned against %@ as well, didn't it?

%@ works by assuming the parameter is an NSObject*, sending it the "description" method which should return an NSString*, and printing the characters in the NSString*. Since you passed a struct que*, sending it a "description" method isn't going to work.

dancks
Apr 25, 2013, 04:23 PM
%@ for NSObject*.
%s for char*
%p to print a pointer.

a->next is neither an NSObject*, nor a char*, but a struct que*. So %p is the only one that would work.

Surely Xcode warned against %@ as well, didn't it?

%@ works by assuming the parameter is an NSObject*, sending it the "description" method which should return an NSString*, and printing the characters in the NSString*. Since you passed a struct que*, sending it a "description" method isn't going to work.

:D

there we go! I didn't want to print out a struct, I wanted to print out the string! lolz. wow. Thanks.

EDIT: hold the phone: for(que *a=top;a->next!=0;a=a->next) NSLog(@"\n%s\n",[a->sentence UTF8String]); crashes. No warnings. Nothing. I'm getting really frustrated.

lee1210
Apr 25, 2013, 08:03 PM
Start much simpler. You're going to a lot of trouble, mixing a lot of C data structures and Objective-C, throwing things at the wall and hoping dearly for a miracle.

Given an NString literal, can you log it?
Now can you do it using %@ as the format specifier?
Now create an NSArray of NSString.
Use fast enumeration to log each element.
Create an NSMutableArray.
Add NSStrings one at a time.
Loop over this with fast enumeration, logging each element.
Try logging one of your NSArrays directly with %@.

Look at the documents for NSString, NSArray, and NSMutableArray. Test the methods. Print lengths. Remove elements. Play with an NSMutableString.

You're expecting things to happen without practice. Read the docs. When something doesn't work right away, spend time figuring it out. Don't fall back to what you already know, it will only confuse you and keep you from the real task: learning. Ask questions here. Post full, compilable code. Do your exercises in main, don't worry about building your own objects yet.

Good luck.
-Lee

subsonix
Apr 25, 2013, 08:49 PM
EDIT: hold the phone: for(que *a=top;a->next!=0;a=a->next) NSLog(@"\n%s\n",[a->sentence UTF8String]); crashes. No warnings. Nothing. I'm getting really frustrated.

You haven't told us under what circumstances it crashes, for example, trying to print an empty list would segfault since *top would be a NULL pointer and a->next is attempting to dereference it.

Some other thoughts, your list methods doesn't need to take a reference to the list itself as arguments, you already have an internal reference with your instance variable *top. This way you can also avoid one level of indirection and hide your internal list representation from your interface.

ArtOfWarfare
Apr 25, 2013, 11:57 PM
EDIT: hold the phone: for(que *a=top;a->next!=0;a=a->next) NSLog(@"\n%s\n",[a->sentence UTF8String]); crashes. No warnings. Nothing. I'm getting really frustrated.

Don't write out so much on a single line - it becomes difficult to read. Your code has no comments in it anywhere which also makes it difficult to read.

What do you think your code does? Write down a list in plain english of the steps that occur in order. I wrote them down in white below - highlight it to read it.

1: (In the Initialization of the for loop) Define a as a pointer to a que structure that is equivalent to top.
2: (In the Condition of the for loop) Check that member next (why are you already looking at member next?) of the que structure pointed at by a is not 0 (is that really what you want? What is 0?). If it is, exit the for loop.
3: (In the Body of the for loop) Invoke UTF8String on member sentence of the que structure pointed at by a, and put the results in a formatted string. (This is an odd way of printing out an NSString. There's a format specifier that you can use to print out any NSObject, which NSString inherits from. Why not use that specifier and pass it the NSString* directly?)
4: (In the Increment of the for loop) Set a as the pointer to whatever member next of a points at.

I've bolded questions in the steps above for you that should hopefully help you fix your mistakes. I wrote it in white because I figured it'd be a good exercise for you to come up with the steps yourself before you look at what I came up with. I listed the steps myself as an exercise for myself, because practice is good for everyone.

dancks
Apr 26, 2013, 11:39 AM
lol. In my defense I did actually get it to work somewhat by replacing NSLog with fprintf. I didn't fully test the linkedlist outside of the project but I'm confident there are no leaks.

Now I get EXC_BAD_ACCESS on return NSApplicationMain(argc, (const char **)argv); after a couple of seconds of testing it out (adding, deleting rows). I wonder if I have to take ownership of and manually release rows in a tableview when I want to remove them.

My understanding of what happened is that when you pass a pointer or object to NSLog, it takes it by reference and destroys it at the end of execution, leaving a dangling pointer. This wouldn't explain why I get a bad pointer with [MyString UTF8String] as that returns a const char*, which can't be destroyed (but can be lost). It doesn't make sense but thats the only thing I can figure.

I just tested:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSString *test = [[NSString alloc] initWithUTF8String:"This is the test string"];
int i;

NSLog(@"First test:\n");
for(i=0;i<4;i++)
{
NSString *ret = [NSString stringWithString:test];
NSLog("\n%@\n",ret);
[ret release];
}

NSLog(@"Second test:\n");
for(i=0;i<4;i++)
{
NSLog("\n%@\n",test);
}

[test release];
[pool drain];
return 0;
}
but I get:

NSStringtest.m:13: warning: passing argument 1 of ‘NSLog’ from incompatible pointer type
NSStringtest.m:20: warning: passing argument 1 of ‘NSLog’ from incompatible pointer type
mac:objective-c jdd$ ./test
2013-04-26 04:35:30.725 test[584:903] First test:
Segmentation fault

whooleytoo
Apr 26, 2013, 12:41 PM
NSLog requires a NSString, not a char*. So, it should be NSLog(@"....") rather than NSLog("...");

chown33
Apr 26, 2013, 12:43 PM
lol. In my defense I did actually get it to work somewhat by replacing NSLog with fprintf. I didn't fully test the linkedlist outside of the project but I'm confident there are no leaks.

Now I get EXC_BAD_ACCESS on return NSApplicationMain(argc, (const char **)argv); after a couple of seconds of testing it out (adding, deleting rows). I wonder if I have to take ownership of and manually release rows in a tableview when I want to remove them.

My understanding of what happened is that when you pass a pointer or object to NSLog, it takes it by reference and destroys it at the end of execution, leaving a dangling pointer. This wouldn't explain why I get a bad pointer with [MyString UTF8String] as that returns a const char*, which can't be destroyed (but can be lost). It doesn't make sense but thats the only thing I can figure.


Explain how you achieved this understanding. Please give a rationale (explanation of your though process).

I think you're guessing, or misunderstanding something you may have read somewhere long ago.

NSLog takes no ownership of anything passed to it. Why would it? It needs no objects or pointers to have a lifetime beyond the single function call. There's no logical reason for it to alter any retain-counts, either by retaining or releasing anything.




In these statements, point out "argument 1 of 'NSLog'":
NSLog("\n%@\n",ret);
NSLog("\n%@\n",test);


Do this before reading further.



Answers in light gray text:

The answer is that "\n%@\n" is argument 1.
What type is it? It's a C string literal (formal type: const char[]).
What type does NSLog need? An NSString object (formal type: NSString *).
How does one write an NSString literal? Like so: @"\n%@\n"



Note that this convoluted code:
NSString *test = [[NSString alloc] initWithUTF8String:"This is the test string"];

is more succinctly expressed as:
NSString *test = @"This is the test string";

You should review the very basic parts of Objective-C, and learn them without thinking of them as related to C. The complex code looks like something a C programmer would write if they only knew about NSString methods, and were unaware of how to write Objective-C NSString literals. Literals are fundamental.


It would also help us understand your learning context if you provided the following:
1. The exact title, author, and edition of the book.
2. What other programming languages you know, and programming experience you have. A complete work history isn't needed. I'm just trying to figure out why your approach seems to have C in it.

dancks
Apr 27, 2013, 12:09 AM
Explain how you achieved this understanding. Please give a rationale (explanation of your though process).

I think you're guessing, or misunderstanding something you may have read somewhere long ago.

NSLog takes no ownership of anything passed to it. Why would it? It needs no objects or pointers to have a lifetime beyond the single function call. There's no logical reason for it to alter any retain-counts, either by retaining or releasing anything.

You should review the very basic parts of Objective-C, and learn them without thinking of them as related to C. The complex code looks like something a C programmer would write if they only knew about NSString methods, and were unaware of how to write Objective-C NSString literals. Literals are fundamental.


It would also help us understand your learning context if you provided the following:
1. The exact title, author, and edition of the book.
2. What other programming languages you know, and programming experience you have. A complete work history isn't needed. I'm just trying to figure out why your approach seems to have C in it.

the first language I tried to learn seriously was C++, so I just feel comfortable with it. It did allow me to eliminate any possible problems with using NSMutableArray. I have an annoying habit of glossing over the small yet still important parts of programming. I seriously toyed with the idea of writing a program that would run through and fix minor typo's and symbols I forgot. Like '@'. I'll get around to it.

Thanks everyone for your patience.

gnasher729
Apr 27, 2013, 01:38 PM
the first language I tried to learn seriously was C++, so I just feel comfortable with it. It did allow me to eliminate any possible problems with using NSMutableArray. I have an annoying habit of glossing over the small yet still important parts of programming. I seriously toyed with the idea of writing a program that would run through and fix minor typo's and symbols I forgot. Like '@'. I'll get around to it.

Thanks everyone for your patience.

Have you tried Xcode? Turn all the warnings on, and turn the static analyzer on as well. Make sure that warnings = errors. Xcode will find an awful lot of bugs, and Xcode will very often suggest the correct fix.

Madd the Sane
Apr 27, 2013, 02:04 PM
You can skip the Xcode step by using Clang. It will show the warnings in a helpful manner in the Terminal.

gnasher729
Apr 27, 2013, 04:47 PM
You can skip the Xcode step by using Clang. It will show the warnings in a helpful manner in the Terminal.

What would be the benefit of that?