views:

360

answers:

8

I'm having trouble figuring out how to pass strings back through the parameters of a function. I'm new to programming, so I imagine this this probably a beginner question. Any help you could give would be most appreciated. This code seg faults, and I'm not sure why, but I'm providing my code to show what I have so far.

I have made this a community wiki, so feel free to edit.

P.S. This is not homework.

This is the original version

#include <stdio.h>

#include <stdlib.h>
#include <string.h>

void
fn(char *baz, char *foo, char *bar)
{
     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     foo = malloc(strlen(pch));
     strcpy(foo, pch);

     pch = strtok (NULL, ":");
     bar = malloc(strlen(pch));
     strcpy(bar, pch);

     return;
}

int
main(void)
{
     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, myfoo, mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);
}

UPDATE Here's an updated version with some of the suggestions implemented:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXLINE         1024

void
fn(char *baz, char **foo, char **bar)
{
     char line[MAXLINE];
     char *pch;

     strcpy(line, baz);

     pch = strtok (line, ":");
     *foo = (char *)malloc(strlen(pch)+1);
     (*foo)[strlen(pch)] = '\n';
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = (char *)malloc(strlen(pch)+1);
     (*bar)[strlen(pch)] = '\n';
     strcpy(*bar, pch);

     return;
}

int
main(void)
{
     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, &myfoo, &mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

     free(myfoo);
     free(mybar);
}
+1  A: 

In C you typically pass by reference by passing 1) a pointer of the first element of the array, and 2) the length of the array.

The length of the array can be ommitted sometimes if you are sure about your buffer size, and one would know the length of the string by looking for a null terminated character (A character with the value of 0 or '\0'.

It seems from your code example though that you are trying to set the value of what a pointer points to. So you probably want a char** pointer. And you would pass in the address of your char* variable(s) that you want to set.

Brian R. Bondy
+1  A: 

You're wanting to pass back 2 pointers. So you need to call it with a pair of pointers to pointers. Something like this:

void
fn(char *baz, char **foo, char **bar) {
   ...
   *foo = malloc( ... );
   ...
   *bar = malloc( ... );
   ...
}
retracile
Boo on your casting of `malloc()` ! BOO I SAY!
Chris Lutz
You know, I'd done that out of habit for so long, I'd ceased to think about it. Thanks for the nudge to revisit that bit of knowledge.
retracile
+5  A: 

First thing, those mallocs should be for strlen(whatever)+1 bytes. C strings have a 0 character to indicate the end, called the NUL terminator, and it isn't included in the length measured by strlen.

Next thing, strtok modifies the string you're searching. You are passing it a pointer to a string which you're not allowed to modify (you can't modify literal strings). That could be the cause of the segfault. So instead of using a pointer to the non-modifiable string literal, you could copy it to your own, modifiable buffer, like this:

char mybaz[] = "hello:world";

What this does is put a size 12 char array on the stack, and copy the bytes of the string literal into that array. It works because the compiler knows, at compile time, how long the string is, and can make space accordingly. This saves using malloc for that particular copy.

The problem you have with references is that you're currently passing the value of mybaz, myfoo, and mybar into your function. You can't modify the caller's variables unless you pass a pointer to myfoo and mybar. Since myfoo is a char*, a pointer to it is a char**:

void
fn(char *baz, char **foo, char **bar) // take pointers-to-pointers

*foo = malloc(...);  // set the value pointed to by foo

fn(mybaz, &myfoo, &mybar);  // pass pointers to myfoo and mybar

Modifying foo in the function in your code has absolutely no effect on myfoo. myfoo is uninitialised, so if neither of the first two things is causing it, the segfault is most likely occurring when you come to print using that uninitialised pointer.

Once you've got it basically working, you might want to add some error-handling. strtok can return NULL if it doesn't find the separator it's looking for, and you can't call strlen with NULL. malloc can return NULL if there isn't enough memory, and you can't call strcpy with NULL either.

Steve Jessop
The seg fault seems to be happening on pch = strtok (baz, ":"); actually... I'm still trying to figure out why
Jenna
Yes, sorry. I only noticed my "second thing" after the first couple of versions of my answer. Hopefully I've explained that.
Steve Jessop
+1  A: 

Ooh yes, little problem there.

As a rule, if you're going to be manipulating strings from inside a function, the storage for those strings had better be outside the function. The easy way to achieve this is to declare arrays outside the function (e.g. in main()) and to pass the arrays (which automatically become pointers to their beginnings) to the function. This works fine as long as your result strings don't overflow the space allocated in the arrays.

You've gone the more versatile but slightly more difficult route: You use malloc() to create space for your results (good so far!) and then try to assign the malloc'd space to the pointers you pass in. That, alas, will not work.

The pointer coming in is a value; you cannot change it. The solution is to pass a pointer to a pointer, and use it inside the function to change what the pointer is pointing to.

If you got that, great. If not, please ask for more clarification.

Carl Smotricz
Don't call them references, you'll just get confused when you dive into C++. C has _pointers_. Arrays decay to _pointers_.
Chris Lutz
Right you are, fixed!
Carl Smotricz
A: 

the code most likely segfaults because you are allocating space for the string but forgetting that a string has an extra byte on the end, the null terminator.

Also you are only passing a pointer in. Since a pointer is a 32-bit value (on a 32-bit machine) you are simply passing the value of the unitialised pointer into "fn". In the same way you wouldn't expact an integer passed into a function to be returned to the calling function (without explicitly returning it) you can't expect a pointer to do the same. So the new pointer values are never returned back to the main function. Usually you do this by passing a pointer to a pointer in C.

Also don't forget to free dynamically allocated memory!!

void
fn(char *baz, char **foo, char **bar)
{
     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     *foo = malloc(strlen(pch) + 1);
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = malloc(strlen(pch) + 1);
     strcpy(*bar, pch);

     return;
}

int
main(void)
{
     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, &myfoo, &mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

     free( myFoo );
     free( myBar );
}
Goz
A: 

Other answers describe how to fix your answer to work, but an easy way to accomplish what you mean to do is strdup(), which allocates new memory of the appropriate size and copies the correct characters in.

Still need to fix the business with char* vs char**, though. There's just no way around that.

Wang
+2  A: 

One thing everyone is overlooking is that you're calling strtok on an array stored in const memory. strtok writes to the array you pass it so make sure you copy that to a temporary array before calling strtok on it or just allocate the original one like:

char mybaz[] = "hello:world";
ThePosey
"Overlooked?". I got there eventually ;-)
Steve Jessop
Together, we are the Uber-mind! No question can withstand the crowdsource's powerful inter... what was that, intneglect? Well, whatever it's called.
Carl Smotricz
@Steve: LOL! I meant it just as a tackon. There were a few things wrong as everyone's noted :)
ThePosey
A: 

The essential problem is that although storage is ever allocated (with malloc()) for the results you are trying to return as myfoo and mybar, the pointers to those allocations are not actually returned to main(). As a result, the later call to printf() is quite likely to dump core.

The solution is to declare the arguments as ponter to pointer to char, and pass the addresses of myfoo and mybar to fn. Something like this (untested) should do the trick:

void
fn(char *baz, char **foo, char **bar)
{
     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     *foo = malloc(strlen(pch)+1);  /* include space for NUL termination */
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = malloc(strlen(pch)+1);  /* include space for NUL termination */
     strcpy(*bar, pch);

     return;
}

int
main(void)
{
     char mybaz[] = "hello:world";
     char *myfoo, *mybar;

     fn(mybaz, &myfoo, &mybar);
     fprintf(stderr, "%s %s", myfoo, mybar);
     free(myfoo);
     free(mybar);
}

Don't forget the free each allocated string at some later point or you will create memory leaks.

To do both the malloc() and strcpy() in one call, it would be better to use strdup(), as it also remembers to allocate room for the terminating NUL which you left out of your code as written. *foo = strdup(pch) is much clearer and easier to maintain that the alternative. Since strdup() is POSIX and not ANSI C, you might need to implement it yourself, but the effort is well repaid by the resulting clarity for this kind of usage.

The other traditional way to return a string from a C function is for the caller to allocate the storage and provide its address to the function. This is the technique used by sprintf(), for example. It suffers from the problem that there is no way to make such a call site completely safe against buffer overrun bugs caused by the called function assuming more space has been allocated than is actually available. The traditional repair for this problem is to require that a buffer length argument also be passed, and to carefully validate both the actual allocation and the length claimed at the call site in code review.

Edit:

The actual segfault you are getting is likely to be inside strtok(), not printf() because your sample as written is attempting to pass a string constant to strtok() which must be able to modify the string. This is officially Undefined Behavior.

The fix for this issue is to make sure that bybaz is declared as an initialized array, and not as a pointer to char. The initialized array will be located in writable memory, while the string constant is likely to be located in read-only memory. In many cases, string constants are stored in the same part of memory used to hold the executable code itself, and modern systems all try to make it difficult for a program to modify its own running code.

In the embedded systems I work on for a living, the code is likely to be stored in a ROM of some sort, and cannot be physically modified.

RBerteig