1

I'm currently learning about how to use pointers and memory allocation in C, and writing a simple code snippet to try to print a string using pointers. What I have now is:

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

char *aString(void)
{
    char str[20];
    strcpy(str, "Hello World!");
    return str;
}

int main(int argc, char **argv)
{
    char *ptr = malloc(20);
    ptr = aString();
    printf("%s\n", ptr);
    free(ptr);
    exit(0);
}

Which just prints an empty line and tells me that the call to free uses an invalid pointer . Could anyone take the time to explain where I'm thinking about things the wrong way?

Edit: Thank you for all the answers, I am reading through them all.

user2789945
  • 527
  • 2
  • 6
  • 23
  • 3
    `free` can only be used on space allocated by `malloc` (and friends). The line `ptr = aString();` means to point `ptr` at a different place than the malloc'd block. It doesn't mean to copy characters . – M.M Sep 28 '15 at 23:36
  • `str[]` is a local variable, on the stack. when it goes 'out of scope', I.E. when the function exits, any access of the variable is undefined behaviour and can lead to a seg fault event. My suggestion: Declare the str[] array in main() and pass a pointer to str[] to the aString() function – user3629249 Sep 30 '15 at 07:50

7 Answers7

3

The array str[20] declared in the function aString() is allocated in the program stack. When the program exits aString(), this array is popped out of memory and is not longer accessible, thereby making ptr point to an invalid pointer.

The malloc() function, on the other hand, allocates memory from the heap, which can be used in aString():

char *aString() {
    char *str = malloc(20);
    strcpy(str, "Hello World!");
    return str;
}

And then in your main():

char *ptr = aString();
printf("%s\n", ptr);
free(ptr);
Community
  • 1
  • 1
John Bupit
  • 10,406
  • 8
  • 39
  • 75
1

There is a good explanation of what the stack and heap are In this link.

Your code is creating the array str[20] on the local stack of aString.

When the function aString calls return the stack it uses is cleared. This includes the array str[20] that you are then trying to use in your main function.

If you want be able to use that array after the function has returned you will need to put the memory on the heap. Which is not cleaned up after the function returns.

Or pass a place to store the array in to the function.

I have included an example of heap allocation below:

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

#define MAX_SIZE 20
char *aString(void)
{
    /**
     * create an array of 20 characters on the heap.
     * This memory is not guaranteed to be all 0.
     * You may want to memset the memory to 0, or use calloc.
     */
    char* str = malloc( MAX_SIZE );

    /* copy a max of 20 characters into the array you just created */
    /* snprintf guarantees a null terminator, which is important. */
    snprintf( str, MAX_SIZE, "Hello World!" );

    return str;
}

int main(int argc, char **argv)
{
    char *ptr;
    ptr = aString();
    printf("%s\n", ptr);

    free(ptr); /* clear the heap memory, this is not needed for stack */
    exit(0);
}
Serdalis
  • 10,296
  • 2
  • 38
  • 58
  • Your use of `strncpy` is incorrect. This function should be avoided anyway because it is convoluted to use it correctly. `strcpy` would be much simpler. – M.M Sep 28 '15 at 23:39
  • @M.M I've been burned by not using `strncpy` far too many times because of bad data will no null termination. I much prefer it over `strcpy` even if it is harder to use. – Serdalis Sep 28 '15 at 23:40
  • @M.M True, I added a `memset` to make it more reliable, and to illustrate that newly allocated memory may not be zero'd out. – Serdalis Sep 28 '15 at 23:44
  • 1
    The same problem still exists, `strncpy` will overwrite all the stuff you just memset if the source string is 20 or more and you will end up with a non null terminated string. – M.M Sep 28 '15 at 23:45
  • 1
    I think this illustrates my point... even when you eventually get the code correct, it'd be easy for someone later to inadvertantly mess it up, not realizing the subtleties of why the code is exactly as it is. (Or someone reading this answer then writing their own code based on it). Personally I would use `snprintf` which has much less room for error. BTW you need to increase your memset amount! – M.M Sep 28 '15 at 23:51
  • 1
    The `memset` is no longer required if you've changed to snprintf. (And some would say that `calloc` is better than malloc followed by memset). – M.M Sep 29 '15 at 00:03
1

str[20]; exists on the stack and it is not valid to reference it after you return from aString.

char *aString(void)
{
    char *str;
    str = malloc(20);
    strcpy(str, "Hello World!");
    return str;
}

int main(int argc, char **argv)
{
    char *ptr = aString();
    printf("%s\n", ptr);
    free(ptr);
    exit(0);
}
LogicG8
  • 1,767
  • 16
  • 26
1

You're thinking incorrectly about two concepts.

  1. Returning an object that is local to a function is a bad idea.

    char *aString(void)
    {
        char str[20];
        strcpy(str, "Hello World!");
        return str;
    }
    

    Because the memory for str is freed after returning from aString, the pointer returned by the function may not point to valid memory anymore.

  2. Copying from a pointer does not automatically copy any value it holds.

    char *ptr = malloc(20);
    ptr = aString();
    

    Ignoring the issue with aString returning a pointer to an object that no longer exists, you're essentially doing this:

    /*
     * NOT valid C syntax
     */
    ptr = AllocateMemory(ByteCount=20)
    // ptr now points to address 0x10 where at least 20 bytes are available.
    
    ptr = aString()
    // ptr now points to address 0x1010. The 20 bytes allocated above can
    // no longer be freed using ptr.
    

The first thing to do is remedy the copying situation:

// Copy a maximum of 20 bytes --
// the number of bytes allocated for the object that "ptr" points to.
strncpy(ptr, aString(), 20);
// If there were 20 bytes copied, and the last one was not the null character,
// "ptr" is not null-terminated. As a result, the string is forcibly truncated here.
// While ordinarily bad design, this is not meant to be robust.
ptr[19] = 0;

However, you're still returning the address of a function-local object, which means strncpy would attempt to copy the nonexistent string returned by astring. Here's the second attempt at fixing the problem:

char *aString (char *s, size_t n) {
    strncpy(s, "Hello World!", n);
    s[n - 1] = 0;
    return s;
}

...

char *ptr = malloc(20);
aString(ptr, 20);
printf("%s\n", ptr);

Also, you really should add a check to ensure malloc did not return a null pointer before attempting to use ptr at all. Most example code omits it for brevity. Some systems always return a non-null pointer, but it's better to be safe anyway. Always check your return values. ;-)

0

On aString, str is allocated on the stack, and is freed automatically by stack unwinding on exit.

Community
  • 1
  • 1
tstark81
  • 478
  • 3
  • 13
0

To be honest, I am not sure how your code compiled without a warning, but when I wrote it and tried to compile, I get a return-local-address warning. Use the -Wall flag to check for all possible warnings.

Essentially what you have done is created a char array, your str[20], and tried to return and use it outside of the scope it was declared inside.

Instead, you could let the aString() take a parameter of type char *string and strcpy into the string directly.

void aString(char *string) {
    strcpy(string, "Hello World!");
}
adamsuskin
  • 547
  • 3
  • 16
  • Right, but then that makes the use of "Hello World" irrelevant, and it should be replaced by string. then I could call strcpy(str, string) inside aString(char *astring), and then use char *ptr = aString("Hello World!") in main. – user2789945 Sep 28 '15 at 23:58
  • 1
    Well it really depended on what you were trying to accomplish. It seems like (from the accepted answer) you are just trying to figure out how to create a string that can be used outside of the scope it is declared within. Glad you found an answer! – adamsuskin Sep 29 '15 at 01:41
0

[Warning! too much explanation]Here is what happens in your code line by line:

char *ptr = malloc(20); memory of size 20 bytes is allocated and ptr points to it.

ptr = aString(); you said ptr no longer points to the 20byte memory that is created but points to this thing called 'aString()' [which makes your first line useless]. so now lets see what aString() has to bring since ptr is about to point to that.

char str[20]; a character array of size 20 is created and str is a pointer to it (is good to remember name of array is just a pointer to the first element).

strcpy(str, "Hello World!"); str points to the array of about 12 characters.

return str; str is returned which is address of first element of the character array. This is just address of a stack memory that holds a local variable which no longer exists once out of the function. so now back to the main

ptr = aString(); now means ptr points to address that has nothing

*ptr = *aString(); // put the content pointed by aString() to ptr 20byte memory.

This would make more sense because it will put the first character H in ptr because that is return str; brings. But if you allocate the memory in the aString function, then you have what you wanted.

char *aString(void)
{
    char *str = malloc(20);
    strcpy(str, "Hello World!");
    return str;
}
int main(int argc, char **argv)
{
    char *ptr;
    ptr = aString();
    printf("%s\n", ptr);
    free(ptr);
    exit(0);
}
Lukas
  • 3,423
  • 2
  • 14
  • 26