1

I need to call a function from the main to determine a string value. However, as far as I know, string cannot be in the form string_name = call_function(). The assignment of string has to be in form of strcpy(str1, str2) right? So I am wondering what am I doing wrong with this strcpy with str1 as the variable name and the str2 is a return string value from another function.

So far, this is what I have.

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

char *get_name(int num);

char *get_name(int num) {
    char real_name[30];
    
    if (num == 1)
        strcpy(real_name, "Jake Peralta");
    
    return real_name;
}

void main() {
    char name[30];
    int num;
    
    num = 1;
    
    strcpy(name, *get_name(num));
    printf("%s", name);
}

It is not printing anything on my output screen.

What I have tried :

  • get_name(num) without using pointers (still does not work)

p/s: This is not the actual code. This is just an example of what I am trying to do, the actual code is longer and I have identified that this part is where the error is coming from.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • The usual way to return strings is have the caller allocate the buffer, and pass a pointer to it and its size as arguments. So you'd have `void get_name(int num, char *real_name, size_t real_name_size) {...}` and call it like `char name[30]; get_name(num, name, sizeof(name));`. – dxiv Mar 07 '21 at 08:04
  • You pass a `char` instead of a `char*`. Your compiler should show some warning. If not, you should turn up warning level. For GCC you can use `-Wall -Wextra` – Gerhardh Mar 07 '21 at 09:24

3 Answers3

2

You are correct, a string cannot be returned as an array via the return statement. When you pass or return an array, only a pointer to its first element is used. Hence the function get_name() returns a pointer to the array defined locally with automatic storage (aka on the stack). This is incorrect as this array is discarded as soon as it goes out of scope, ie: when the function returns.

There are several ways for get_name() to provide the name to its caller:

  • you can pass a destination array and its length: and let the function fill the name in the array, carefully avoiding to write beyond the end of the array but making sure it has a null terminator:

     char *get_name(char *dest, size_t size, int num) {
         if (num == 1) {
             snprintf(dest, size, "Jake Peralta");
         } else {
             snprintf(dest, size, "John Doe");
         }
         // return the destination pointer for convenience.
         return dest;
     }
    
     int main() {
         char name[30];
         int num = 1;
         get_name(name, sizeof name, num);
         printf("%s\n", name);
         return 0;
     }
    
  • you can allocate memory in get_name() and return a pointer to the allocated array where you copy the string. It will be the caller's responsibility to free this object with free() when it is no longer used.

     char *get_name(int num) {
         if (num == 1) {
             return strdup("Jake Peralta");
         } else {
             return strdup("John Doe");
         }
     }
    
     int main() {
         int num = 1;
         char *name = get_name(num);
         printf("%s\n", name);
         free(name);
         return 0;
     }
    
  • you can return a constant string, but you can only do this if all names are known at compile time.

     const char *get_name(int num) {
         if (num == 1) {
             "Jake Peralta";
         } else {
             "John Doe";
         }
     }
    
     int main() {
         int num = 1;
         const char *name = get_name(num);
         printf("%s\n", name);
         return 0;
     }
    
chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

You are returning address of real_name from get_name method which will go out of scope after the function returns. Instead allocate the memory of string on heap and return its address. Also, the caller would need to free the string memory allocated on the heap to avoid any memory leaks.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
Amit Rastogi
  • 926
  • 2
  • 12
  • 22
1

Like you said, "However, as far as I know, string cannot be in the form string_name = call_function()." To understand the logic behind this, just look at your get_name() function:

char *get_name(int num)
{
    char real_name[30];
    
    if (num==1)
        strcpy(real_name,"Jake Peralta");
    
    return real_name;
}

Here, you try to return the starting address of real_name, but real_name is destroyed when it goes out of scope (in this case, when the function returns). There are two ways I think you could fix this. One is to add the string that should hold the return value as a parameter. In your case, it would be:

void get_name(int num, char *dest)
{
    char real_name[30];
    
    if (num==1)
    {
        strcpy(real_name,"Jake Peralta");
        strcpy(dest, real_name);
    }
}

Or, just avoid using real_name at all now to make the function shorter:

void get_name(int num, char *dest)
{
    if (num==1)
        strcpy(dest, "Jake Peralta");
}

The other way is to allocate the return value on the heap, but I wouldn't recommend this; you would have to keep track of each allocated string and eventually free all of them. Nevertheless, here is how it would look like in your case:

char *get_name(int num, char *dest)
{
    char *real_name = calloc(30, sizeof(char)); // Using calloc to avoid returning an uninitialized string if num is not 1
    if (num==1)
        strcpy(real_name, "Jake Peralta";
    return real_name;
}

By the way, I kept your strcpy here, but might want to avoid using it in the future, as it can cause buffer overruns. Here's a post with useful answers as to why it's bad. In fact, even strncpy isn't fully safe (See here (credit to @chqrlie for providing the link)). @chqrlie provides a clean and safe alternative using snprintf in his own answer, I'd suggest you use that.

mediocrevegetable1
  • 4,086
  • 1
  • 11
  • 33
  • Please do not advocate the use of `strncpy` to beginners. https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/ – chqrlie Mar 07 '21 at 09:01
  • @chqrlie I am aware of the "NULL termination not guaranteed issue", which is why I have NULL-terminated manually. Furthermore, I have also noted that `strncpy` is not completely safe either. Reason I didn't use `strlcpy` or `strcpy_s` is that one isn't standard at all and one is an "optional" addition to the c11 standard respectively, and I'm not exactly sure if OP has either. Nonetheless, I understand that this is a beginner. Should I comment out the last segment of the answer or remove it altogether? – mediocrevegetable1 Mar 07 '21 at 09:08
  • Unlike August, you did warn the OP about the inherent unsafe semantics of `strncpy`, but I'm afraid these considerations are beyond their current skill level. `strcpy_s` is definitely not a palatable alternative and `strlcpy` is not always available. `if (dest_len) { *dest = '\0'; strncat(dest, str, dest_len - 1); }` is portable but convoluted and confusing as the third argument is **not** the size of the destination array. Sadly, `snprintf()` seems to be the only standard function to copy with truncation, shame on the committee for being so conservative with historical shortcomings! – chqrlie Mar 07 '21 at 09:19
  • I would recommend the OP to not use `strncpy()` with a caveat about it not null terminating the destination array and needlessly padding it with nulls if its size is larger than the string length, but I would not provide even a safe coding sample. The question you point to is just about as confusing as the issue. Better point to [this one](https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/) – chqrlie Mar 07 '21 at 09:21
  • @chqrlie I had actually not thought about `snprintf` at all :P. You're right though, it is a much cleaner alternative. I see you' ve already provided a good example of it, so I'll remove my safe example and point towards yours. – mediocrevegetable1 Mar 07 '21 at 09:27
  • Thanks for the credit, the article isn't mine... in your last example, you should always store a string into `real_name` to avoid returning a pointer to uninitialized memory. – chqrlie Mar 07 '21 at 13:36
  • @chqrlie I thought as much, I just gave credit for _providing_ the link. As for the uninitialized part, you're right, that slipped my mind. I replaced the `malloc` with `calloc` to zero-initialize the whole string. – mediocrevegetable1 Mar 07 '21 at 13:51