Thursday, November 19, 2009

return string from a function

return string from a function

Hello... I found that I could return a string literal form a function using the functions char* return variable only without passing a string address to the function... I was suprised because I thought it would pass out of scope as the function ended.... but it works for a printf (below).

So I thought this might work for a 'char string[x]' as well.... but I haven't been able to get anything to work. I stare at it and wonder WHY? What have I done to displease you ??????..... Your brother "1234" behaves... Why can't YOU? really..What would be the difference between "1234" and those same chars in an array? How can I get that array to look like the string literal ??? Below I have tried three different methods in three functions. One and three work. Functoin two is what I've been banging my head on:

CPP / C++ / C Code:
/* return_pointer.c */
#include

char* func1(void);
char* func2(void);
char* func3(char str[9]);

int main(void)
{
char *cptr, str[9];
printf("\nIn main: func1()= >%s< \n\n", func1() ); cptr = func2(); printf("\nIn main func2(): cptr= >%s< addr:%p func2()= >%s< \n", cptr, (void*)cptr, func2() ); printf("\nIn main: func3(str)= >%s< \n", func3(str)); return 0; } /****** works OK ******************/ char* func1(void) { char *cp1; cp1 = "1001"; /* is this a string literal? */ return cp1; } /****** can't get this working **********************/ char* func2(void) { char *cp2, txt[5] = {'1','1','1','1',0}; /* *cp2 = txt ; : assignment makes integer from pointer without a cast */ /* cp2 = &txt; : assignment from incompatible pointer type */ cp2 = txt; printf("In func2(): txt = >%s< addr: txt=%p cp2=%p \n", txt, (void*)txt, (void*)cp2); return cp2; /* no warning reported */ /* return txt; : function returns address of local variable */ /* return &txt; : return from incompatible pointer type : function returns address of local variable */ /* return *txt; : return makes pointer from integer without a cast */ } /****** works ok ***********************/ char* func3(char str[9]) { int i; for(i = 0; i < 8; i++) str[i] = 0x32; str[8] = '\0'; return str; } Output: Code: -------------------------------------------------------------------------------- In main: func1()= >1001< In func2(): txt = >1111< addr: txt=0073FD80 cp2=0073FD80 In func2(): txt = >1111< addr: txt=0073FD80 cp2=0073FD80 In main func2(): cptr= >°²s< addr:0073FD80 func2()= >°²s< In main: func3(str)= >22222222< -------------------------------------------------------------------------------- Am I whippin' a dead horse here? . . ... you say "just bite the bullet and pass an address" ? Well, my intended functions purpose would be to generate a digit string for %s output to a printf as shown. It will already have three arguments passed to it, so it would be nice not to have to have a fourth. Thanks, Howard; WaltP 17-Aug-2007 13:20 -------------------------------------------------------------------------------- Re: return string from a function Quote: -------------------------------------------------------------------------------- Originally Posted by Howard_L Hello... I found that I could return a string literal form a function using the functions char* return variable only without passing a string address to the function... I was suprised because I thought it would pass out of scope as the function ended.... but it works for a printf (below). -------------------------------------------------------------------------------- That's because a string literal has a static location. So you are returning a pointer to a location that is persistent. Quote: -------------------------------------------------------------------------------- Originally Posted by Howard_L So I thought this might work for a 'char string[x]' as well.... but I haven't been able to get anything to work. I stare at it and wonder WHY? What have I done to displease you ??????..... Your brother "1234" behaves... Why can't YOU? really..What would be the difference between "1234" and those same chars in an array? How can I get that array to look like the string literal ??? -------------------------------------------------------------------------------- An array is destroyed (or at least available to be reused immediately) when the function returns. Therefore -- stop beating your head against a wall. If you make the array static, you will have no problem... statics are persistent. Howard_L 17-Aug-2007 13:40 -------------------------------------------------------------------------------- Re: return string from a function Quote: -------------------------------------------------------------------------------- -- stop beating your head against a wall. If you make the array static, you will have no problem... statics are persistent. -------------------------------------------------------------------------------- Ah YES! ...I'll-be-dog... Head feels much better now... Thanks Walt. I had not though of trying a static, and while I knew a static must be somewhere in memory for a function to be able to pick up values where it left off on it's last call, but I didn't realize that there could be other uses for that characteristic such as a pointer to that space could be picked up and used in another function. CPP / C++ / C Code: added to main(): printf("cptr after assignment to func2()= >%s< \n", cptr); cptr[1] = 0x32; printf("cptr after assignment to func2()= >%s< \n", cptr); added to func3(): static char txt[5] = {'1','1','1','1',0}; Code: -------------------------------------------------------------------------------- In main: func1()= >1001< In func2(): txt = >1111< addr: txt=00402010 cp2=00402010 In func2(): txt = >1111< addr: txt=00402010 cp2=00402010 In main func2(): cptr= >1111< addr:00402010 func2()= >1111< cptr after assignment to func2()= >1111< cptr after assignment to func2()= >1211< In main: func3(str)= >22222222< -------------------------------------------------------------------------------- I note the different address range from the earlier declaration... Besides printf being able to print the string, I can even use the pointer elsewhere in the program as illustrated by the use in assignment to 'char *cptr' in main() and the printf(). Neat stuff, thanks again! ++Howard; davekw7x 17-Aug-2007 13:44 -------------------------------------------------------------------------------- Re: return string from a function Quote: -------------------------------------------------------------------------------- Originally Posted by Howard_L I found that I could return a string literal -------------------------------------------------------------------------------- The string literal is located in some place in the data segment of the compiled code. It should actually be "read-only", but some compilers do not enforce the "read-only" part. It could be considered dangerous to pass a pointer to a string literal unless you use the "const" qualifier, so a better programming practice might be something like the following: CPP / C++ / C Code: char const *func1(void); . . . char const *func1(void) { char *cp1; cp1 = "1001"; /* is this a string literal? */ return cp1; } You are telling the compiler that the function is returning a pointer to an array whose contents will not change. Then try the following in your main(): CPP / C++ / C Code: int main(void) { char *cptr, str[9]; cptr = func1(); printf("\nIn main: func1()= >%s< \n\n", cptr); cptr[1] = 'X'; printf("After assignment, cptr: >%s>\n", cptr);


You should get compiler warnings about the assignment statement for cptr = func1(). These are warnings, and in fact by declaring things the way that you did, the result of cptr[1] = 'X' is undefined behavior. Since they are only "warnings", an executable is generated, and you can run it.

Current versions of GNU compilers actually have a separate read-only data space, and if I run the program, I get a "STATUS_ACCESS_VIOLATION" error and stackdump. Borland and Microsoft compilers that I have will cheerfully allow you to )illegally) change the contents of memory where the string literal resides.

As I have mentioned about a million times (maybe more), undefined behavior is undefined. It is not guaranteed to cause an error, but it might cause an error (or it might, under some circumstances, lead to apparently correct results). By declaring a pointer to a const array, at least the compiler can be helpful enough to give a warning in case I have a mental lapse (or if other programmers want to use my beautiful function) and try to change something that I had previously promised not to.

With your original definitions (no const), none of the compilers gave a warning. The results were, of course, the same: ACCESS_VIOLATION runtime error with gcc, benign behavior with the others.

Bottom line: If you are going to return a pointer to some read-only memory (like a string literal), it is safer to use "const" in the declaration/definition. (And, don't (don't) ignore the compiler warnings.)


Quote:

--------------------------------------------------------------------------------

Originally Posted by Howard_L
So I thought this might work for a 'char string[x]'
--------------------------------------------------------------------------------



Not a chance. Well let me be a little more precise and say that this is unconditionally, absolutely undefined behavior. You are setting the value of a pointer to an address of local automatic storage (usually said to be "on the stack"). You return the value of the pointer. But the memory that this is pointing can no longer be legally accessed. Period. Full stop.

Quote:

--------------------------------------------------------------------------------

Originally Posted by Howard_L
What would be the difference between "1234" and those same chars in an array?
--------------------------------------------------------------------------------


In func1()

CPP / C++ / C Code:
char *cp1;
cp1 = "1001"; /* is this a string literal? */

The value of cp1 is the address in program data memory of some "stuff". You pass the value of that address back to the calling function.

In func2()

CPP / C++ / C Code:
char *cp2;
char txt[5] = { '1', '1', '1', '1', 0 };
cp2 = txt;
You have an array in local automatic memory. You initialize the array with some "stuff".
You set a pointer value to be the address of the first element of the array.

So far, so good.

Now, you pass the value of that address back to the calling function. The memory that was assigned to the local array txt[] is no longer legally accessible.

Quote:

--------------------------------------------------------------------------------

Originally Posted by Howard_L
How can I get that array to look like the string literal ???
--------------------------------------------------------------------------------


I don't really like your terminology. An array is a block of memory (somewhere). A string literal is a sequence of chars between quotation marks.

If you want to initialize an array with a string literal, you could do something like

CPP / C++ / C Code:
char txt[5] = "1111";

or, let the compiler count for you, and just simply say

CPP / C++ / C Code:
char txt[] = "1111";

and the result would be exactly the same as it was with your statement:

CPP / C++ / C Code:
char txt[5] = { '1', '1', '1', '1', 0 };

Yes, exactly.

In either case, the array will go out of scope at the end of the function/block where the array is declared, so you can't legally access it after returning to the calling function.

Now, there is a way to make the array not only available, but read-writeable outside its function.

Actually there are two ways. Neither is recommended as a general programming practice, for various structured programming reasons.

1. Declare the array outside the function in such a way that it is visible to other functions in the file. Declaring it before main(), would give it file scope (sometimes called a "global" variable, since it is available to all functions in the program).

By declaring it globally and not inside any function, you wouldn't have to pass pointers to it to or from functions. I hate to repeat myself, but this is generally not recommended as a general-purpose Good Thing to Do.

That's not to say that one should never use global variables, but getting into the habit of using globals just for the heck of it (or just to avoid having to pass a pointer as a function argument) may very well lead to grief some day. My friendly advice: Don't show up for a design review or a peer code review with a lot of global variables unless you are ready to defend their use. (Free advice, freely given, and it is, after all, only an opinion.)

2. Declare the array to be static inside the function. The array will reside somewhere in program data space (not the stack), and the contents of the array will be unchanged from one function call to the next, and if you pass a pointer to that memory back to the calling function, it will be valid for access on other places in the program. Note that if you initialize the array in the declaration statement inside the function, the memory is initialized at load time and not re-initialized when you call the function.

As in the case of global variables, static variables in functions may present some difficulties. In addition to making debugging more problematic, they really play havoc with programs written with threads (--don't get me started---see footnote), and they make the functions non-reentrant. If you don't know or don't care about such things, then don't worry about it now. But some day you may care. Or, maybe, not.

My friendly advice: In general, if you don't need to change the contents of the array outside of the function, just use a string literal and return a pointer as you did in your first example.

If you do need to change the contents, inside or outside the function, then pass a pointer into the function as an argument.

Your func3 works because you "did it right". However, I have one final comment. You should realize that the number in the brackets, [8], is irrelevant. The function will always get a pointer to char. Yes, always.

You could declare/define func3 the way that you did, but I would rather see either


CPP / C++ / C Code:
char *func3(char str[])
or

CPP / C++ / C Code:
char *func3(char *str)

All three ways (your way and my two examples) are exactly the same to the compiler.

The reason that I don't like your way (especially on a forum that is visited by less-experienced programmers trying to figure out what the heck we are talking about) is that sometimes people tend to think that defining it the way that you did tells the function that it the parameter is an array of eight chars. It's really important to know that this is simply not true. There is no way (no way) that a function can know the size of an array in its argument list unless you tell it (probably with another parameter). The array gets a pointer to char. (But I already said that.)


Quote:

--------------------------------------------------------------------------------

Originally Posted by Howard_L
It will already have three arguments passed to it, so it would be nice not to have to have a fourth.
--------------------------------------------------------------------------------



Well, I don't believe that using global variables or static variables as a general program practice is justified by someone's perception of "niceness". If your function is going to operate on an array, I say pass it a pointer to the array.

Of course that's just an opinion, and as always, Your Mileage May Vary.

Regards,

Dave

Footnote:
"A computer is a state machine.
Threads are for people who can't program state machines."
---Linux kernel guru, and fine human being, Alan Cox

Howard_L 18-Aug-2007 00:56

--------------------------------------------------------------------------------

Re: return string from a function

I thought this might have been a redundant thread to start but has turned out to be very concise. Excellent replies you guys. I understand MUCH more now! And it's making sense!

Even though static works well, I think I'll just be going for the fourth argument in the call in a continuing effort to learn good programming practices. Learn to practice, practice to learn...

Speaking of which I'll be getting back to finishing up that function which goes into the darn project (which also had the #ifdef etc.) I started days ago. I keep running into these interesting forks in the road....a day here , a day there and another week goes by....
Thanks Again, ++Howard;

No comments: