【问题标题】:How to have a call function as the second string in strcpy?如何将调用函数作为 strcpy 中的第二个字符串?
【发布时间】:2021-06-05 09:58:03
【问题描述】:

我需要从 main 调用一个函数来确定一个字符串值。但是,据我所知,字符串不能采用string_name = call_function() 的形式。字符串的分配必须采用strcpy(str1, str2) 的形式,对吗?所以我想知道这个strcpystr1 作为变量名,而str2 是另一个函数的返回字符串值,我做错了什么。

到目前为止,这就是我所拥有的。

#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);
}

它没有在我的输出屏幕上打印任何东西。

我尝试过的:

  • get_name(num) 不使用指针(还是不行)

p/s:这不是实际代码。这只是我正在尝试做的一个例子,实际代码更长,我已经确定这部分是错误的来源。

【问题讨论】:

  • 返回字符串的常用方法是让调用者分配缓冲区,并将指向它的指针及其大小作为参数传递。所以你会有void get_name(int num, char *real_name, size_t real_name_size) {...} 并像char name[30]; get_name(num, name, sizeof(name)); 一样称呼它。
  • 您传递的是 char 而不是 char*。您的编译器应该显示一些警告。如果没有,您应该调高警告级别。对于 GCC,您可以使用 -Wall -Wextra

标签: c function strcpy


【解决方案1】:

您是对的,字符串不能通过return 语句作为数组返回。当您传递或返回一个数组时,只使用指向其第一个元素的指针。因此,函数get_name() 返回一个指向本地定义的自动存储数组的指针(又名在堆栈上)。这是不正确的,因为这个数组一旦超出范围就会被丢弃,即:当函数返回时。

get_name() 可以通过多种方式向调用者提供名称:

  • 您可以传递目标数组及其长度:并让函数将名称填充到数组中,小心避免超出数组末尾,但确保它有一个空终止符:

     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;
     }
    
  • 您可以在get_name() 中分配内存并返回一个指向您复制字符串的已分配数组的指针。当不再使用该对象时,调用者将负责使用free() 释放该对象。

     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;
     }
    
  • 您可以返回一个常量字符串,但只有在编译时所有名称都已知时才能这样做。

     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;
     }
    

【讨论】:

  • 我使用了第二个选项,它有效!非常感谢!
【解决方案2】:

您正在从get_name 方法返回real_name 的地址,该方法将在函数返回后超出范围。而是在堆上分配字符串的内存并返回其地址。此外,调用者需要释放在堆上分配的字符串内存以避免任何内存泄漏。

【讨论】:

    【解决方案3】:

    就像你说的,“但是,据我所知,字符串不能采用string_name = call_function() 的形式。”要了解这背后的逻辑,只需查看您的 get_name() 函数:

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

    在这里,您尝试返回real_name 的起始地址,但real_name 在超出范围时被销毁(在这种情况下,当函数返回时)。我认为有两种方法可以解决这个问题。一种是添加应该保存返回值的字符串作为参数。在您的情况下,它将是:

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

    或者,现在完全避免使用real_name 来缩短函数:

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

    另一种方法是在堆上分配返回值,但我不建议这样做;您必须跟踪每个分配的字符串并最终释放所有字符串。不过,这就是您的情况:

    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;
    }
    

    顺便说一句,我将您的strcpy 保留在这里,但以后可能希望避免使用它,因为它可能导致缓冲区溢出。 Here's a post with useful answers as to why it's bad。事实上,即使strncpy 也不是完全安全的(参见here(感谢@chqrlie 提供链接))。 @chqrlie 在他自己的回答中使用snprintf 提供了一个干净安全的替代方案,我建议你使用它。

    【讨论】:

    • 请不要提倡初学者使用strncpyrandomascii.wordpress.com/2013/04/03/stop-using-strncpy-already
    • @chqrlie 我知道“NULL 终止不保证问题”,这就是我手动终止 NULL 的原因。此外,我还注意到strncpy 也不是完全安全的。我没有使用strlcpystrcpy_s 的原因是一个根本不是标准的,一个是c11 标准的“可选”补充,我不确定OP 是否有。尽管如此,我知道这是一个初学者。我应该注释掉答案的最后一部分还是完全删除它?
    • 与 August 不同,您确实警告过 OP 关于 strncpy 固有的不安全语义,但恐怕这些考虑超出了他们目前的技能水平。 strcpy_s 绝对不是一个可口的选择,strlcpy 并不总是可用的。 if (dest_len) { *dest = '\0'; strncat(dest, str, dest_len - 1); } 是可移植的,但令人费解且令人困惑,因为第三个参数不是目标数组的大小。可悲的是,snprintf() 似乎是唯一可以用截断复制的标准函数,委员会对历史缺陷如此保守感到羞耻!
    • 我建议 OP 不要使用 strncpy() 并注意它不是 null 终止目标数组并在其大小大于字符串长度时不必要地用 null 填充它,但我不会甚至提供安全的编码示例。您指出的问题与问题一样令人困惑。最好指向this one
    • @chqrlie 实际上我根本没想过snprintf:P。你是对的,它是一个更清洁的选择。我看到你已经提供了一个很好的例子,所以我将删除我的安全例子并指向你的。
    猜你喜欢
    • 2020-02-17
    • 2020-08-28
    • 1970-01-01
    • 2018-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-12
    • 1970-01-01
    相关资源
    最近更新 更多