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

11

我在弄清楚如何通过函数参数传递字符串时遇到了麻烦。我是编程新手,所以我想这可能是一个初学者问题。如果您能提供任何帮助,我将不胜感激。以下代码导致段错误,但我不确定原因,但我提供我的代码以展示我目前的情况。

我已经将其设为社区 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段错误,请查看我下面的建议。 - ThePosey
8个回答

13

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

接下来,strtok 修改了你正在搜索的字符串。你正在传递一个指向字符串的指针,你不能修改它(你不能修改文字字符串)。这可能是 segfault 的原因。所以,你可以将其复制到自己的可修改缓冲区中,而不是使用指向不可修改字符串文字的指针,就像这样:

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 调用 strlen。如果没有足够的内存,malloc 可以返回 NULL,并且您也不能使用 NULL 调用 strcpy。

段错误似乎发生在pch = strtok(baz, ":");上...实际上,我仍在努力弄清楚为什么。 - Jenna
是的,抱歉。我在回答的前几个版本中才注意到我的“第二件事”。希望我已经解释清楚了。 - Steve Jessop

2
哦,有点小问题。
一般来说,如果你要在函数内部操作字符串,那么这些字符串的存储空间最好在函数外部。实现这一点的简单方法是在函数外部声明数组(例如在main()中),并将数组(自动变为指向其开头的指针)传递给函数。只要结果字符串不超出分配在数组中的空间,这种方法就可以正常工作。
你选择了更加灵活但稍微困难一些的路线:使用malloc()创建结果的空间(到目前为止都很好!),然后尝试将malloc的空间分配给你传入的指针。然而,这样做是行不通的。
传入的指针是一个值;你不能改变它。解决方案是传递一个指向指针的指针,并在函数内部使用它来改变指针所指向的内容。
如果你理解了这个,太棒了。如果没有,请再询问更多的澄清。

1
不要称它们为引用,当你深入学习C++时,你会感到困惑。C有指针。数组会衰变成指针。 - Chris Lutz

2

大家忽略的一个问题是,你正在对存储在常量内存中的数组调用 strtok。strtok会向其传递的数组中写入数据,因此在调用 strtok之前,请确保将其复制到临时数组中,或者只需分配原始数组,例如:

char mybaz[] = "hello:world";

被忽略了?最终我还是解决了它;-) - Steve Jessop
一起,我们是超级大脑!没有问题能够抵挡众包的强大互动……那个叫什么,intneglect?嗯,不管它怎么称呼。 - Carl Smotricz
@Steve:哈哈!我只是想加一句。正如大家指出的那样,有几个问题存在。 - ThePosey

1

你想要返回2个指针。因此,你需要使用一对指向指针的指针来调用它。就像这样:

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

你对malloc()的转换有问题,我要喊BOO! - Chris Lutz
你知道吗,我已经习惯这样做很长时间了,以至于我已经不再思考它。感谢你提醒我重新审视这一点知识。 - retracile

1

在C语言中,通常通过引用传递参数是通过传递1)数组第一个元素的指针和2)数组的长度来实现的。

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

从您的代码示例中看来,您似乎正在尝试设置指针所指向的值。因此,您可能需要一个char **指针,并传入要设置的char *变量的地址。


0

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

此外,您只传递了一个指针。由于指针是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 );
}

0

问题的关键在于,虽然使用malloc()为你试图返回为myfoomybar分配了存储空间,但这些分配的指针实际上没有返回到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中,无法被物理修改。


0
其他答案描述了如何修复您的答案以使其正常工作,但实现您想要做的事情的简单方法是使用strdup(),它会分配适当大小的新内存并复制正确的字符。
不过,仍然需要解决char*与char**之间的业务问题。这是无法避免的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接