Thread: Returning "string" (or character array?) from a function / malloc() question.

  1. #1
    Registered User
    Join Date
    Jul 2017
    Posts
    9

    Returning "string" (or character array?) from a function / malloc() question.

    I am trying to figure out a good way (as in good practices I suppose) to return "string" information from a function. And by string, I think I probably mean a character array. After a bit of searching around, I have something that appears to work, but I'm not sure if it's written well. And more importantly, I'm not sure if I'm freeing memory when I'm done with the call.

    In my demo code below (which compiles and works), I'm sending an integer to a function, and based on the value of that integer, I'm returning a string? character array? pointer?

    I got this working using malloc, which is brand new to me. For as long as I've tinkered with C, I've heard of malloc many times, but have never used it myself. So this is unfamiliar territory.

    I believe I need to use free() after using malloc(), but I'm not sure where to use it.

    So my questions are:
    1. despite the fact that this works (for me), is it obviously bad code or broken in some way?
    2. Do I need to use free(). If so, where do I use it? I tried using free(diceDamage) in main() just before return 0, but that doesn't work. And it doesn't seem logical to use free() at the end of the getDamage() function because that seems like it would free() the memory before it got a chance to be used.

    Code:
    #include <stdio.h>
    #include <stdlib.h>  // needed by malloc()
    
    char *getDamage(int level) {
    
        // Allocate memory for 9 characters + 1 more character
        // for the string terminator \0
        char *diceDamage =  malloc((9 + 1) * sizeof(char));
    
        if (diceDamage == NULL) {
            return NULL;
        }
    
        if (level > 1 && level <= 3) {
            diceDamage = "dice(1,4)";
        } else if (level > 3 && level <= 5) {
            diceDamage = "dice(1,5)";
        } else {
            diceDamage = "dice(2,7)";
        }
    
        return diceDamage;
    }
    
    int main(void) {
    
        int baseDamage  = 4; // Example of base damage
        int playerLevel = 3; // Example of player's level
        char *diceDamage = getDamage(playerLevel);
    
        printf("Total damage: %d + %s\n", baseDamage,diceDamage);
    
        return 0;
    }

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    Your main error is that you are trying to assign to your allocated string by using =. You need to use strcpy instead. In that case putting the free just before the return 0 in main would be correct.

    However, if you want to return a string literal then you don't need dynamic memory.
    Code:
    #include <stdio.h>
     
    const char *getDamage(int level) {
        const char *str = "<bad level>";
        if (level >= 1) {
            if      (level <= 3) str = "dice(1,4)";
            else if (level <= 5) str = "dice(1,5)";
            else if (level <= 7) str = "dice(2,7)"; // guessing max level is 7
        }
        return str;
    }
     
    int main() {
        int level = 0;
        while (printf("Level: "), scanf("%d", &level) == 1)
            printf("%s\n", getDamage(level));
        putchar('\n');
        return 0;
    }
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Registered User
    Join Date
    Jul 2017
    Posts
    9
    Quote Originally Posted by john.c View Post
    Your main error is that you are trying to assign to your allocated string by using =. You need to use strcpy instead. In that case putting the free just before the return 0 in main would be correct.

    However, if you want to return a string literal then you don't need dynamic memory.
    Ok thanks for the help. I suppose if I can do what I'm trying to do without needing malloc, then I won't bother with it.

    I used your example to create this:

    Code:
    #include <stdio.h>
    
    
    const char *getDamage(int level) {
    
    
        const char *str = "<bad level>";
    
    
        if (level >= 1) {
            if      (level <= 3) str = "dice(1,4)";
            else if (level <= 5) str = "dice(1,5)";
            else if (level <= 7) str = "dice(2,7)";
        }
    
    
        return str;
    }
    
    
    int main(void) {
    
    
        int baseDamage  = 4;
        int playerLevel = 3;
        const char *diceDamage = getDamage(playerLevel);
    
    
        printf("Total damage: %d + %s\n", baseDamage,diceDamage);
    
    
        return 0;
    }
    So it seems one of the main things I was missing before was having "const" before the function and before the char declarations. So I guess that placing const before the function and char declarations makes them, constant? I guess when the function ends, it normally deletes the memory space? So that is why I can't return str without having the function "const"?

    I'm just trying to understand how return str; or return "dice(1,4)" is any different from return 1; or return 7; or whatever.

  4. #4
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    Quote Originally Posted by blixel View Post
    So it seems one of the main things I was missing before was having "const" before the function and before the char declarations. So I guess that placing const before the function and char declarations makes them, constant? I guess when the function ends, it normally deletes the memory space? So that is why I can't return str without having the function "const"?
    Not exactly. The pointer needs to be const because a string literal cannot be written to. But the basic reason a string literal can be returned from a function is because its data is not stored on the stack. The string of characters in a string literal is saved in a global section of memory (like the "heap" but the data doesn't change) and everywhere it is referred to in the program, a pointer to it is used. Since the string literal always exists whether or not the function you declare it in is active, you can return it's address from the function. It's only data on the stack that becomes inaccessible when the function corresponding to that stack frame returns.

    Quote Originally Posted by blixel View Post
    I'm just trying to understand how return str; or return "dice(1,4)" is any different from return 1; or return 7; or whatever.
    Returning 1 and returning "hello" (the address of a string literal) are essentially identical in that they both return a single number, in the one case an integer, in the other a (const) pointer to a string of characters.
    Last edited by john.c; 03-12-2019 at 03:04 PM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  5. #5
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,412
    I suggest a different approach:
    Code:
    #include <stdio.h>
    
    typedef struct {
        int min;
        int max;
    } DamageRange;
    
    DamageRange *getDamageRange(DamageRange *damageRange, int level) {
        if (level < 1 || level > 7) {
            return NULL;
        }
    
        if (level <= 3) {
            damageRange->min = 1;
            damageRange->max = 4;
        } else if (level <= 5) {
            damageRange->min = 1;
            damageRange->max = 5;
        } else {
            damageRange->min = 2;
            damageRange->max = 7;
        }
    
        return damageRange;
    }
    
    int main(void) {
        int baseDamage  = 4;
        int playerLevel = 3;
        DamageRange damageRange;
    
        if (getDamageRange(&damageRange, playerLevel)) {
            printf("Total damage: %d + dice(%d,%d)\n", baseDamage, damageRange.min, damageRange.max);
        } else {
            printf("Invalid player level: %d\n", playerLevel);
        }
    
        return 0;
    }
    What you were doing was to come up with a particular string format for a "damage range" in your getDamage function. This might be convenient for your current purposes, but it is also limiting: if later you want to generate random numbers so as to compute the actual damage rather than have the user roll dice, you have to change your code considerably to accommodate that.

    What I did instead was to model the damage range in a struct, then rename getDamage to getDamageRange so as to reflect what it is really producing. The string format is then expressed in the format string of the printf call, so if say, you decide that at one place you want to print "dice(1,4)" but in another place you prefer to print "roll the die for a random number from 1 to 4", that's not a challenge at all since you can have two different printf calls with their own format strings, with the same damage range.
    Last edited by laserlight; 03-12-2019 at 03:32 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  6. #6
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    That's a way better idea. The actual changing information is kept in the most useful form.
    A little inaccuracy saves tons of explanation. - H.H. Munro

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. A "Simple" Array/String Problem. "Help" ASAP needed
    By AirulFmy in forum C Programming
    Replies: 10
    Last Post: 08-19-2015, 04:30 AM
  2. Replies: 4
    Last Post: 06-13-2012, 09:58 PM
  3. Replies: 46
    Last Post: 08-24-2007, 04:52 PM

Tags for this Thread