【问题标题】:How do I copy a char string from another function to main?如何将 char 字符串从另一个函数复制到 main?
【发布时间】:2018-07-14 21:09:36
【问题描述】:

我正在尝试使用 strcpy 将 char 从 char 类型函数复制到 main 函数中的 var,但程序总是给我一个随机值作为返回值,程序就完成了。我在代码中是否遗漏了一些语法错误,或者可能是其他方式来完成此副本?

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

char text()
{
    char palavra1[]= "tata";
    return palavra1;
}

char main()
{
    char saida[50];
    strcpy(saida,text());   // this line is supposed to copy the string from text to the var saida
    printf("%s", saida);
    return 0;
}

【问题讨论】:

  • 你必须返回char*,你不能声明palavral本地到text()并返回任何东西(当函数返回时text的所有内存都被销毁了)。将指向具有足够存储空间的数组的指针传递给text 并将palavral 复制或声明为指针并使用malloc 分配并返回指向main 的指针(例如char *palavral = malloc (strlen ("tata") + 1); strcpy (palvral, "tata"); return palvral;)您负责将内存释放回main 完成后。

标签: c string


【解决方案1】:

在函数text()中,palavra1是存放在test()s栈帧的栈中,所以当你返回字符串回到main的栈帧时,@987654325占用的内存@ 不再由编译器保留,并且可以并且将通过调用 main 中的其他函数来覆盖。要解决这个问题,你有两个选择,要么让text 的调用者提供一个指针来存储字符串,或者让text malloc() 为字符串提供一些内存并返回,依赖调用函数来释放完成后。就个人而言,我建议使用第一种方法,因为这意味着您完成后不必担心freeing 指针,但这确实是您的选择。

此外,您的函数返回类型都是错误的。 text() 应该返回一个char*,你应该希望从你的编译器中得到一个警告。最后,main() 在 ISO 标准中指定为int main(void)int main(int argc, char** argv)。偏离这两个签名中的任何一个通常都是不好的做法。

【讨论】:

  • 这在很大程度上是正常的,但是您引用的堆栈不是 C 规范的一部分;它是实现 C 标准所要求的东西的一种手段。在 C 语言中,test 内部定义的 palavra1 对象在 test 返回时不复存在,因此不能用于返回字符串。
  • 还有第三种返回字符串的选项;可以在静态持续时间的对象中返回它。这不需要解释,因为它通常应该避免,但这意味着说只有两个是不正确的。答案可能会说“要解决这个问题,你有两个不错的选择……”然后提到“从技术上讲,第三种方法是在具有静态存储持续时间的数组中返回一个字符串,但这通常不是好的设计,所以不是在这里解释。”
【解决方案2】:

palavral 具有自动存储持续时间 - 意味着当函数结束时它将被销毁(更清楚的是声明它的块结束但这里函数体是声明它的块 - 所以当函数结束时它是随着块的结束,生命周期将结束)。现在您返回了指向局部变量的指针——精确指向函数结束后将不存在的内存,并尝试通过在strcpy 上使用它来访问它——这是undefined behavior

事实上标准是这样说的:-

从§6.2.4你可以看到未定义的行为

一个对象在其生命周期之外被引用

这是问题所在:- 但有几个解决方案。如果您想返回一些指针,那么您可以动态分配内存并返回已分配块的地址,或者将数组 pass 设为静态或全局。

动态内存分配的讨论:

您可以使用malloc/calloc/reallocstrdup(内部使用其中一个)创建字符串的副本,其存储持续时间超出其声明的范围并将其返回。 (一种选择是动态内存分配并将其用于从函数返回)。(此代码显示在答案的后面部分)

关于将内存设为静态还是全局的讨论。

使其具有静态存储持续时间是另一种选择。 (通过显式使用 static 关键字将数组设为全局或将其设为静态。)。

从§6.2.4p3点下静态存储时长

...它的生命周期是程序的整个执行过程,它的存储值仅在程序启动之前初始化一次。

最后一行是什么意思?它(局部变量)的生命周期不会结束,除非程序结束,这意味着我们可以从函数中返回它。 (是的,这是一个解决方案,但您不会看到它 - 这不是做事的好方法)。

动态分配代码

const char*p = "data";
char *s = malloc(strlen(p)+1);
if(!s){
   perror("malloc");
   exit(EXIT_FAILURE);
}
memcpy(s,p,strlen(p)+1);
return s;

main()

 char *p = text();
 if(p!=NULL)
     strcpy(saida,p);
 free(p);

静态存储时长示例代码:

在这种情况下,除了使指针变量static 之外,没有什么可显示的。

static char palavar1[]="tata";
...
return palavar;

不使用所有这些困难的词 - 可以说现在这个局部变量即使在函数结束时也将保持活动状态。之前讨论的问题现在不会成为问题。它还能活多久?直到程序结束。因此,您可以安全地使用上述技术而放弃 在其生命周期之外访问对象。

其他解决方案

其他解决方案将简单地传递将被修改的缓冲区,字符串文字内容将使用strcpymemcpy 复制到其中。没有必要退货。这个解决方案我在第一次编辑时没有讨论,因为它是实现你想要做的有点不同的方法。

标准指示 main 的返回类型为 int 而不是 char。理想情况下,int main(void) 可以。

【讨论】:

  • 如果有办法我会很高兴,尊敬的反对者能告诉我哪里错了吗?
  • 我没有投反对票,但我可以猜到:进入 POSIX 事情有点偏离轨道。只要有良好的严格 C 答案,坚持使用它们就很好。这个答案没有提到调用者提供使用缓冲区的选项。选民可能不喜欢措辞和段落结构。 6.2.4 中的引用可能不是教授static 的好方法。教人可能很难。您想分小步走,连接起来形成一条从他们所在的地方到他们应该去的地方的路径。
  • @EricPostpischil.: 恐怕我现在不能编辑..我到这里再回来。感谢您的建议。
  • @EricPostpischil.:我有一些时间并进行了一些编辑。我想这至少对 dv 检索有好处。时间允许的时候再补充。谢谢。
【解决方案3】:

每个函数都可以返回自己的类型,但它不能返回指向在其函数堆栈中分配的内存的指针,因为函数堆栈被释放以在函数返回时重用并且不再可访问。任何访问其内存不再可访问的值的尝试都是未定义行为

您有两个选择,或者 (1) 将 saida 作为参数传递给 text,(例如 char *text (char *s) { strcpy (s, "tata"); return s }(此时函数可以声明为 void,因为复制到 s 的字符将无论如何都可以在调用者中使用-并且不需要main()中的strcpy),或者(2)动态分配内存以在text()中保存"tata"并返回一个指向新块的指针内存。使用malloc, calloc, or realloc 分配的内存在函数返回后仍然存在,因为它分配在堆上并且具有程序持续时间(或直到它被释放)。

(注意:第三个选项是将palavra1声明为static字符数组,其大小足以容纳"tata"——这也将导致它在函数返回中幸存下来。但是通常应避免这种做法,而应优先选择前两个选项)

您可以以简单的方式动态分配,例如:

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

char *text(void)
{
    char *p = "tata",   /* delcare pointer to string literal "tata" */
        *palavra1 = malloc (strlen (p) + 1);    /* allocate storage */

    if (palavra1 != NULL)       /* validate malloc succeeded */
        strcpy (palavra1, p);   /* copy "tata" to palavra1 */

    return palavra1;
}

int main(void)
{
    char saida[50],
        *p = text();    /* must preserve pointer to beginning of memory */

    /* validate p not NULL and length < 50 */
    if (p != NULL && strlen (p) < sizeof saida) {
        strcpy (saida, p);          /* copy to saida */
        printf ("%s\n", saida);     /* output */
        free (p);                   /* free allocated memory */
    }

    return 0;
}

使用/输出示例

$ ./bin/allocinfn
tata

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/allocinfn
==13720== Memcheck, a memory error detector
==13720== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==13720== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==13720== Command: ./bin/allocinfn
==13720==
tata
==13720==
==13720== HEAP SUMMARY:
==13720==     in use at exit: 0 bytes in 0 blocks
==13720==   total heap usage: 1 allocs, 1 frees, 5 bytes allocated
==13720==
==13720== All heap blocks were freed -- no leaks are possible
==13720==
==13720== For counts of detected and suppressed errors, rerun with: -v
==13720== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

另请注意:main 的正确声明是 int main (void)int main (int argc, char **argv)(您将看到用等效的 char *argv[] 编写)。注意: maintype int 的函数,它返回一个值。参见:C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570)。另请参见:See What should main() return in C and C++?)

检查一下,如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案4】:

    即使您将text() 的返回类型固定为

    char *text()
    {
        char palavra1[]= "tata";
        return palavra1;
    }
    

    这仍然是错误的。原因是palavra1 是一个局部变量 在text() 中,并且它仅在text() 返回之前有效且可访问。在那之后 指向palavra1 的指针指向一个无效位置,用它来读/写 值将导致未定义的行为。

    从函数返回字符串有两种方法。

    1. 函数用malloc分配内存并返回一个指向 分配的内存。调用text()的函数需要释放内存:

      char *text(void)
      {
          char *ptr, palavra1[] = "tata";
      
          ptr = malloc(strlen(palavra1) + 1);
          if(ptr == NULL)
              return NULL;
      
          strcpy(ptr, palavra1);
          return ptr;
      }
      
      int main(void)
      {
          char saida[50];
          char *res = text();
      
          if(res == NULL)
          {
              fprintf(stderr, "failed to allocate memory\n");
              return 1;
          }
      
          strncpy(saida, res, sizeof saida);
          saida[sizeof(saida) - 1] = 0; // making sure to get a valid string
      
          puts(saida);
      
          free(res);
      
          return 0;
      }
      
    2. 第二个选项是传递一个指向函数的指针,让函数 通过指针写入内容:

      int text(char *buffer, size_t length)
      {
          palavra1[] = "tata";
      
          if(buffer == NULL)
              return 0;
      
          strncpy(buffer, palavra1, length);
          buffer[length - 1] = 0; // making sure to get a valid string
          return 1;
      }
      
      int main(void)
      {
          char saida[50];
      
          text(saida, sizeof saida);
      
          puts(saida);
      
          return 0;
      }
      
    3. 第三个选项(我知道我说过 2)是在 text() 中声明 palavra1 作为static char[] = "tata";。为了完整起见,我添加了第三个 选项,但我建议您不要使用它,因为它还有其他问题 你不想处理的,例如当你的函数需要 以嵌套的方式调用。所以最好忽略它,直到你真正知道并且 了解如何处理static 变量。

    在这两种情况下我都使用了strncpy,因为使用它你可以限制 因此写入的字节可防止缓冲区溢出。但是,strncpy 可能不会 如果目标缓冲区不够大,则写入'\0'-终止字节, 这就是为什么您必须确保字符串是'\0'-终止于 将 0 写入目标缓冲区的最后一个位置。

    另外,声明main函数的正确方法是:

    • int main(void);
    • int main(int argc, char **argv);
    • int main(int argc, char *argv[]);

    我以前从未见过char main(),这对我来说是第一次。那是 不正确,你应该改变它。

    【讨论】:

    • 这是目前最好的答案,在正确性、彻底性和结构方面。我不知道为什么其他人没有投票。除非代码有问题——我没有彻底检查过。但是,如果有人注意到了什么,他们应该说出来。
    【解决方案5】:

    您可以将其设为静态(当然这是指针):

    static char palavra1[]= "tata"; 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-25
      • 2014-01-30
      • 2015-08-22
      • 1970-01-01
      • 1970-01-01
      • 2013-04-14
      • 2010-10-27
      相关资源
      最近更新 更多