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

char * upperCase(const char* s)
{
    char * ret = NULL;
    size_t length = strlen(s) + 1;
    ret = (char*)malloc(sizeof(char) * length);
    for (size_t i = 0; i < length; i++) {
        ret[i] = toupper(s[i]);
    }
    return ret;
}

int main()
{
    char* ret = NULL;
    char* input = "HelloWorld";
    ret = upperCase(input);
    printf("value = %s", ret);
    free(ret);
}

The above code takes a string as a parameter and copies the string. It then converts each element of the copied string to uppercase and returns it.

Compiler = GCC 6.2

If you use the malloc function, is it possible to return a local variable to terminate the function and release the memory? I do not have enough understanding of the scope that still has memory.

Community
  • 1
  • 1
YunjinJang
  • 165
  • 2
  • 7
  • 4
    Yes, you can do that. The issue is with returning pointers to things that don’t exist anymore, but what you `malloc` continues to exist until you `free` it. Another option is to take an output pointer as an argument so the memory management is left to the caller. – Ry- Aug 02 '17 at 02:24
  • `sizeof (char)` is 1 by definition. – melpomene Aug 02 '17 at 02:26
  • 2
    `toupper(s[i])` has undefined behavior if `s[i]` is negative. You should cast it to `unsigned char`. – melpomene Aug 02 '17 at 02:26
  • 1. Did the compiler complain? 2. Did gdb complain? If 1 and 2 were both NO, you're good to go. – cs95 Aug 02 '17 at 02:26
  • 6
    @cᴏʟᴅsᴘᴇᴇᴅ That seems like very bad advice. – melpomene Aug 02 '17 at 02:28
  • It's a start... – cs95 Aug 02 '17 at 02:28
  • 1
    @COLDSPEED. I'm confused. Are you saying "If a C program compiles then it can't have bugs or leaks?" Are you saying "You should always run your C program under a debugger"? – John3136 Aug 02 '17 at 02:33
  • 1
    YunjinJang Curious: why `(char*)` cast in `ret = (char*)malloc(sizeof(char) * length);`? Why multiple by `sizeof(char)` which is 1? Who or what text suggested that? – chux - Reinstate Monica Aug 02 '17 at 02:39
  • @chux The reason for casting is that I know that the "malloc" function requires casting because the return type is "void *". And I use "sizeof ()" because I have seen a lot of things that are mainly used for other types, not char types. I was mistaken as to how to use char type like this too. – YunjinJang Aug 02 '17 at 02:52
  • 1
    @YunjinJang In C, the cast is _not_ required. Consider `ret = malloc(sizeof *ret * length);` for its ease to code right, review and maintain. – chux - Reinstate Monica Aug 02 '17 at 03:02
  • @chux Thanks for your advice. – YunjinJang Aug 02 '17 at 04:13
  • @melpomene why should s[i] be negative if the input is an ascii string? wouldn't casting to unsigned char be obsolete? – CIsForCookies Aug 02 '17 at 04:47
  • 1
    @CIsForCookies *if the input is an ascii string* - yes, "if". Why make such an assumption in a general `upperCase` function? ASCII only covers half of the possible `char` values on my system. – melpomene Aug 02 '17 at 09:45

2 Answers2

6

Short answer: Yes, it is possible

Long answer: malloc(some_size) allocates some_size space and returns a pointer to the address of the start of the allocated chunk (or NULL upon failure). When doing ret = (char*)malloc(sizeof(char) * length); ret is assigned with said pointer that points to memory chunk of length chars (note that sizeof(char) == 1 so you can remove it).

The memory is yours until you free it, even after the function had returned, so, after the end of upperCase(...) execution, that memory still belongs to you. The only problem is, the pointer ret was allocated on the stack with (auto) local storage, meaning it will "die" when it's scope (in your case - the upperCase(...) function's scope), and thus, you will not know where that memory is, BUT since you return ret from the function, the value it holds (which is the address to your allocated memory) will pass on to main's ret, which basically means you're good to go.

One last thing I think I should emphasize is, returning local variables is done all the time. For example int increment_by_one(int x){int y = x + 1; return y;}. This is a very simple function that returns the value of local int y. Since the value returned is all we need (i.e. the value of x+1) it's ok that it (the value) was stored in a local variable. The same (sort of) goes for your case - since ret inside upperCase holds the address that was allocated ,it's ok for it to "die" after upperCase ends, since the value it holds (the address) is passed on.

char * upperCase(const char* s)
{
    char * ret = NULL;                             // local variable -> will die after upperCase ends
    size_t length = strlen(s) + 1;                 // local variable -> will die after upperCase ends
    ret = (char*)malloc(sizeof(char) * length);    // ret assigned with address to memory
    for (size_t i = 0; i < length; i++) {          // local variable -> will die after it's scope (the for loop) ends
        ret[i] = toupper(s[i]);
    }
    return ret;                                     // said address is returned to main (local variable ret now dies peacefully after fulfilling its duty) 
}

int main()
{
    char* ret = NULL;               // local variable -> will die after main ends
    char* input = "HelloWorld";     // local variable -> will die after main ends
    ret = upperCase(input);         // ret gets the address allocated in upperCase
    printf("value = %s", ret);
    free(ret);                      // address is freed
}

2 notes:

  1. There is no need casting malloc return value, meaning ret = (char*)malloc(sizeof(char) * length); should be ret = malloc(sizeof(char) * length);
  2. no need for sizeof(char) since it's 1, meaning you can shorten it further to ret = malloc(length);
  3. malloc can fail. In that case it will return NULL so after every ret = malloc(...); you should check the value like if(!ret){// handle error} or if(ret != NULL){// do something} and such
CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • 1
    There is NO need to cast the return of `malloc`, it is unnecessary. See: [**Do I cast the result of malloc?**](http://stackoverflow.com/q/605845/995714) and ***always*** validate the return of `malloc` (e.g. in `main()` (before `printf` and `free`) you need, at minimum, `if (ret) {/* do stuff */}` or `if (!ret) { /* handle error, or exit */ }`. Otherwise good answer and effort. – David C. Rankin Aug 02 '17 at 05:20
  • @DavidC.Rankin saying casting malloc is unnecessary is exactly what I meant. I'll edit to make it clearer. About the `if(!ret)`, I mentioned that `malloc` can return NULL, but I didn't want to change the OP's code – CIsForCookies Aug 02 '17 at 05:24
  • 2
    Yes, it looked like a copy/paste issue from the original question, but it is always worth cleaning up so learning by the OP or many who may follow will occur `;)` – David C. Rankin Aug 02 '17 at 05:26
4

Yes, you can.

The malloc memory is on the heap, so it won't release when upperCase end.

But variable char *ret is on the stack, it will be popped out when upperCase end. But the value of ret in upperCase() is copied via ret = upperCase(input) in main(). That is to say, you still obtain the address of the allocated memory, which is still on the heap. You can call free(ret) when finished the use of this memory.


Check What and where are the stack and heap? for details about stack & heap.

delta
  • 3,778
  • 15
  • 22