tags:

views:

1589

answers:

9

As a follow-up to this question:

From what I've seen, this should work as expected:

void greet(){
  char c[] = "Hello";
  greetWith(c);
  return;
}

but this will cause undefined behavior:

char *greet(){ 
  char c[] = "Hello";
  return c;
}

If I'm right, what's the best way to fix the second greet function? In an embedded environment? On a desktop?

A: 

Allocate the character array in the heap?

Whether you can use malloc or not depends on just what you mean by "embedded environment".

dmckee
+10  A: 

If you are using C++, then you may want to consider using std::string to return strings from your second function:

std::string greet() {
    char c[] = "Hello";
    return std::string(c); // note the use of the constructor call is redundant here
}

Or, in a single threaded environment you can do:

char *greet() {
    static char c[] = "Hello";
    return c;
}

The static here allocates space in the global memory area, which never disappears. This static method is fraught with peril, though.

Greg Hewgill
Less perilous if you make it `const` as well, and, define it at global scope.
ChrisW
I would have thought it was better to assume it was C, since it's embedded.
Jesse Pepper
Jesse: The "c++" tag sort of implies it's an option.
Greg Hewgill
@ChrisW: I was thinking of the situation where greet() does something nontrivial and returns a computed result of some kind. It's got to store it somewhere non-const, and you have to take care of the foo(greet(), greet()) case too.
Greg Hewgill
+10  A: 

Depends if the embedded environment has a heap or not, if so, you should malloc as follows:

char* greet()
{
  char* ret = malloc(6 * sizeof(char)); // technically " * sizeof(char)" isn't needed since it is 1 by definition
  strcpy(ret,"hello");
  return ret;
}

Note that you should later call free() to clean up. If you don't have access to dynamic allocation, you will need to make it a global, or a variable on the stack, but further up the call stack.

Jesse Pepper
Ah. Better and less snarky then my answer.
dmckee
Would I have to null check ret after the malloc?
@nate: Yes, you certainly should. Null will be returned if it failed to allocate.
Jesse Pepper
@jesse: you're using calloc syntax with malloc... malloc only takes one parameter, the size of the block to allocate in bytes.
Jason Coco
Indeed: calloc(6, sizeof(char)) or malloc(6 * sizeof(char)). In C++, if there is no heap, you could probably override operator new to work in predefined memory (possibly reserved space lower down on the stack or something)
Dan
@jason, dan... whoops
Jesse Pepper
+17  A: 

You're absolutely right. Your c array in the second example is being allocated on the stack, and thus the memory will get reused immediately following. In particular, if you had code like

 printf("%s\n",greet());

you'd get weird results, because the call to printf would have reused some of the space of your array.

The solution is to allocate the memory somewhere else. For expample:

char c[] = "Hello";

char * greet() {
    return c;
}

Would work. Another choice would be to allocate it statically in scope:

char * greet() {
    static char c[] = "Hello";
    return c;
}

because static memory is allocated separately from the stack in data space.

Your third choice is to allocate it on the heap via malloc:

char * greet() {
   char * c = (char *) malloc(strlen("Hello")+1);  /* +1 for the null */
   strcpy(c, "Hello");
   return c;
}

but now you have to make sure that memory is freed somehow, or else you have a memory leak.

Update

One of those things that seems more confusing than I expect is what exactly a "memory leak" is. A leak is hen you allocate memory dynamically, but lose the address so it can't be freed. None of these examples necessarily has a leak, but only the third one even potentially has a leak, because it's the only one that allocates memory dynamically. So, assuming the third implementation, you could write this code:

{
    /* stuff happens */
    printf("%s\n", greet());
}

This has a leak; the pointer to the malloc'ed memory is returned, the printf uses it, and then it's lost; you can't free it any longer. On the other hand,

{
    char * cp ;
    /* stuff happens */
    cp = greet();
    printf("%s\n", cp);
    free(cp);
}

doesn't leak, because the pointer is saved in an auto variable cp long enough to call free() on it. Now, even though cp disappears as soon as execution passes he end brace, since free has been called, the memory is reclaimed and didn't leak.

Charlie Martin
Does the 3rd example leak or do they all do?
Ólafur Waage
None of them leak necessarily; only the third one potentially leaks. A leak is when a reference to dynamically allocated memory is lost so it can't be freed; the third option would leak IFF the returned pointer was lost before the memory was freed.
Charlie Martin
I would just add that while the second example doesn't leak, I believe it does allocate memory that remains allocated for the lifetime of the application, which could result in leak-like symptoms.
Dave Costa
Nonsense. It allocates static memory *once*. It has *exactly* the same behavior as the first example, except that the name is only in scope within the braces.
Charlie Martin
+5  A: 

If you need to do this in C++, using std::string as suggested by Greg Hewgill is definitely the most simple and maintainable strategy.

If you are using C, you might consider returning a pointer to space that has been dynamically allocated with malloc() as suggested by Jesse Pepper; but another way that can avoid dynamic allocation is to have greet() take a char * parameter and write its output there:

void greet(char *buf, int size) {
    char c[] = "Hello";

    if (strlen(c) + 1 > size) {
        printf("Buffer size too small!");
        exit(1);
    }

    strcpy(buf, c);
}

The size parameter is there for safety's sake, to help prevent buffer overruns. If you knew exactly how long the string was going to be, it would not be necessary.

j_random_hacker
+3  A: 

Returning malloc'd memory from a function as suggested by several of the other responses is just asking for a memory leak. The caller would have to know you malloc'd it and then call free. If they happened to call delete on it, the results are undefined (although probably okay).

It would be better to force the caller to provide the memory for you and then copy your string into it. This way the caller is on notice that he/she needs to clean up.

Steve Rowe
+4  A: 

You can use either of these:

char const* getIt() {
    return "hello";
}

char * getIt() {
    static char thing[] = "hello";
    return thing;
}

char * getIt() {
    char str[] = "hello";
    char * thing = new char[sizeof str];
    std::strcpy(thing, str);
    return thing;
}

shared_array<char> getIt() {
    char str[] = "hello";
    shared_array<char> thing(new char[sizeof str]);
    std::strcpy(thing.get(), str);
    return thing;
}

The first requires you to not write to the returned string, but also is the simplest you can get. The last uses shared_array which automatically can clean up memory if the reference to the memory is lost (the last shared_array to it goes out of scope). The last and second last must be used if you require a new string everytime you call the function.

Johannes Schaub - litb
A: 

This is taking a pointer to a char array and copying into the stack variable c[] and then returning a pointer to c

char *greet(){ 
  char c[] = "Hello";
  return c;
}

You might like to try the following for the fun of it - it may actually show you a warning describing the error your having.

typedef char smallstring[6];
smallstring greet(){ 
    smallstring c = "Hello";
    return c;
}
Greg Domjan
+1  A: 

From what I've read the safest option is to make the caller responsible for allocating memory to hold the string. You must also check if the buffer is large enough to hold your string:

char *greet(char *buf, int size) {
     char *str = "Hello!"
     if (strlen(str) + 1 > size) { // Remember the terminal null!
          return NULL;
     } 
     strcpy(buf, str);
     return buf;
}

void do_greet() {
    char buf[SIZE];
    if (greet(buf, SIZE) == NULL) {
        printf("Stupid C");
     } 
     else {} // Greeted!
}

A ton of work for a simple task... but there's C for you :-) Oops! Guess I was beaten by random_hacker...

I've heard that you shouldn't assign a string literal to a char*, but instead, a char[], right?
drhorrible
They mean different things. If you assign a literal to a char *, you get a pointer to the statically allocated read-only literal. If you assign to a char[] you get your own stack-allocated local copy to do with what you will.
Eclipse