在 C 中通过引用传递字符串

Posted

技术标签:

【中文标题】在 C 中通过引用传递字符串【英文标题】:pass strings by reference in C 【发布时间】:2010-12-24 04:49:47 【问题描述】:

我无法弄清楚如何通过函数的参数将字符串传回。我是编程新手,所以我想这可能是一个初学者问题。您可以提供的任何帮助将不胜感激。这个代码段错误,我不知道为什么,但我提供我的代码来显示我到目前为止所拥有的。

我已将其设为社区 wiki,因此请随时编辑。

附:这不是家庭作业。

这是原版

#include <stdio.h>

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

void
fn(char *baz, char *foo, char *bar)

     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     foo = malloc(strlen(pch));
     strcpy(foo, pch);

     pch = strtok (NULL, ":");
     bar = malloc(strlen(pch));
     strcpy(bar, pch);

     return;


int
main(void)

     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, myfoo, mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

更新这是一个更新版本,其中包含一些建议:

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

#define MAXLINE         1024

void
fn(char *baz, char **foo, char **bar)

     char line[MAXLINE];
     char *pch;

     strcpy(line, baz);

     pch = strtok (line, ":");
     *foo = (char *)malloc(strlen(pch)+1);
     (*foo)[strlen(pch)] = '\n';
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = (char *)malloc(strlen(pch)+1);
     (*bar)[strlen(pch)] = '\n';
     strcpy(*bar, pch);

     return;


int
main(void)

     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, &myfoo, &mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

     free(myfoo);
     free(mybar);

【问题讨论】:

对于您的 strtok 段错误,请查看下面我的建议 【参考方案1】:

首先,这些 malloc 应该用于 strlen(whatever)+1 字节。 C 字符串有一个 0 字符表示结束,称为 NUL 终止符,它不包含在 strlen 测量的长度中。

接下来,strtok 修改你正在搜索的字符串。您正在向它传递一个指向不允许修改的字符串的指针(您不能修改文字字符串)。这可能是段错误的原因。因此,您可以将其复制到自己的可修改缓冲区,而不是使用指向不可修改字符串文字的指针,如下所示:

char mybaz[] = "hello:world";

它的作用是将一个大小为 12 的字符数组放入堆栈,并将字符串文字的字节复制到该数组中。它之所以有效,是因为编译器在编译时就知道字符串有多长,并且可以相应地腾出空间。这节省了对该特定副本使用 malloc 的时间。

引用的问题是您当前正在将 mybaz、myfoo 和 mybar 的 传递到您的函数中。除非将 指针 传递给 myfoo 和 mybar,否则不能修改调用者的变量。由于 myfoo 是 char*,指向它的指针是 char**:

void
fn(char *baz, char **foo, char **bar) // take pointers-to-pointers

*foo = malloc(...);  // set the value pointed to by foo

fn(mybaz, &myfoo, &mybar);  // pass pointers to myfoo and mybar

在代码中修改函数中的 foo 对myfoo 绝对没有影响。 myfoo 未初始化,因此如果前两件事都没有导致它,那么当您使用该未初始化的指针进行打印时,很可能会发生段错误。

一旦你让它基本上工作了,你可能想要添加一些错误处理。如果strtok 没有找到它正在寻找的分隔符,它可以返回NULL,并且你不能用NULL 调用strlenmalloc 可以在内存不足的情况下返回 NULL,你也不能使用 NULL 调用 strcpy

【讨论】:

段错误似乎发生在 pch = strtok (baz, ":");实际上......我仍在试图找出原因 是的,对不起。在我的答案的前几个版本之后,我才注意到我的“第二件事”。希望我已经解释过了。【参考方案2】:

每个人都忽略的一件事是,您正在对存储在 const 内存中的数组调用 strtok。 strtok 会写入您传递给它的数组,因此请确保在对其调用 strtok 之前将其复制到临时数组中,或者只分配原始数组,例如:

char mybaz[] = "hello:world";

【讨论】:

“被忽略了?”。我最终到了那里;-) 一起,我们就是 Uber-mind!毫无疑问,可以承受众包强大的交互……那是什么,integlect?好吧,不管它叫什么。 @Steve:哈哈!我的意思只是作为一种策略。每个人都指出了一些问题:)【参考方案3】:

哦,是的,小问题。

通常,如果您要从函数内部操作字符串,这些字符串的存储最好在函数外部。实现这一点的简单方法是在函数外部声明数组(例如在main() 中)并将数组(自动成为指向它们开始的指针)传递给函数。只要您的结果字符串不溢出分配在数组中的空间,这就可以正常工作。

您走的是更通用但稍微困难的路线:您使用malloc() 为您的结果创建空间(到目前为止很好!)然后尝试将 malloc 的空间分配给您传入的指针。唉,那是行不通的。

进来的指针是一个值;你不能改变它。解决方法是传递一个指针给一个指针,并在函数内部使用它来改变指针指向的内容。

如果你明白了,那就太好了。如果不是,请要求更多说明。

【讨论】:

不要称它们为引用,当您深入研究 C++ 时,您只会感到困惑。 C 有指针。数组衰减为指针【参考方案4】:

在 C 中,您通常通过引用传递 1) 数组第一个元素的指针,以及 2) 数组的长度。

如果您确定缓冲区大小,有时可以省略数组的长度,并且可以通过查找以空字符结尾的字符(值为 0 或 '\0' 的字符)来知道字符串的长度。

从您的代码示例看来,您正在尝试设置指针指向的值。所以你可能想要一个char** 指针。您将传入您要设置的char* 变量的地址。

【讨论】:

【参考方案5】:

您想要传回 2 个指针。所以你需要用一对指向指针的指针来调用它。像这样的:

void
fn(char *baz, char **foo, char **bar) 
   ...
   *foo = malloc( ... );
   ...
   *bar = malloc( ... );
   ...

【讨论】:

malloc() 的选角!嘘! 你知道,我这样做是出于习惯很久了,我已经不再去想它了。感谢您推动重新审视这些知识。【参考方案6】:

代码最有可能出现段错误,因为您正在为字符串分配空间,但忘记了字符串末尾有一个额外的字节,即空终止符。

另外,您只是传入一个指针。由于指针是一个 32 位值(在 32 位机器上),您只需将未初始化指针的值传递给“fn”。以同样的方式,您不会将传递给函数的整数解释为返回给调用函数(没有显式返回它),您不能指望指针做同样的事情。所以新的指针值永远不会返回给主函数。通常,您通过将指针传递给 C 中的指针来做到这一点。

别忘了释放动态分配的内存!!

void
fn(char *baz, char **foo, char **bar)

     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     *foo = malloc(strlen(pch) + 1);
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = malloc(strlen(pch) + 1);
     strcpy(*bar, pch);

     return;


int
main(void)

     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, &myfoo, &mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

     free( myFoo );
     free( myBar );

【讨论】:

【参考方案7】:

其他答案描述了如何修复您的工作答案,但完成您意思要做的事情的简单方法是 strdup(),它分配适当大小的新内存并复制正确的字符在。

不过,仍然需要使用 char* 与 char** 来修复业务。没有办法解决这个问题。

【讨论】:

【参考方案8】:

基本问题是,尽管曾经为您尝试以myfoomybar 返回的结果分配存储空间(使用malloc()),但指向这些分配的指针实际上并未返回给main()。因此,稍后对printf() 的调用很可能会转储内核。

解决方案是将参数声明为指向char 的指针,并将myfoomybar 的地址传递给fn。像这样(未经测试)的东西应该可以解决问题:

void
fn(char *baz, char **foo, char **bar)

     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     *foo = malloc(strlen(pch)+1);  /* include space for NUL termination */
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = malloc(strlen(pch)+1);  /* include space for NUL termination */
     strcpy(*bar, pch);

     return;


int
main(void)

     char mybaz[] = "hello:world";
     char *myfoo, *mybar;

     fn(mybaz, &myfoo, &mybar);
     fprintf(stderr, "%s %s", myfoo, mybar);
     free(myfoo);
     free(mybar);

不要忘记稍后释放每个分配的字符串,否则会造成内存泄漏。

要在一次调用中同时执行 malloc() 和 strcpy(),最好使用strdup(),因为它还记得为您在编写的代码中遗漏的终止 NUL 分配空间。 *foo = strdup(pch) 比替代方案更清晰,更容易维护。由于strdup() 是 POSIX 而不是 ANSI C,因此您可能需要自己实现它,但这种用法的清晰性得到了很好的回报。

从 C 函数返回字符串的另一种传统方法是让调用者分配存储空间并将其地址提供给函数。例如,这是sprintf() 使用的技术。它的问题是,假设分配的空间比实际可用的空间多,则无法使这样的调用站点完全安全地防止被调用函数引起的缓冲区溢出错误。解决此问题的传统方法是要求同时传递缓冲区长度参数,并在代码审查中仔细验证实际分配和调用站点声明的长度。

编辑:

您得到的实际段错误可能在strtok() 内,而不是printf(),因为您编写的示例正试图将字符串常量传递给必须能够修改字符串的strtok()。这是正式的未定义行为。

解决此问题的方法是确保 bybaz 被声明为初始化数组,而不是指向 char 的指针。初始化的数组将位于可写内存中,而字符串常量可能位于只读内存中。在许多情况下,字符串常量存储在用于保存可执行代码本身的同一部分内存中,现代系统都试图让程序难以修改自己的运行代码。

在我以谋生为生的嵌入式系统中,代码很可能存储在某种 ROM 中,并且无法进行物理修改。

【讨论】:

以上是关于在 C 中通过引用传递字符串的主要内容,如果未能解决你的问题,请参考以下文章

在Java中通过引用传递字符串?

在C中通过引用传递一个char数组

在C中通过引用传递数组?

在 C 中通过引用传递时会发生啥?

在 C++11 中通过引用 std::thread 传递对象

在python中通过引用传递引用