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

时间:2009-12-07 21:40:01

标签: c string pass-by-reference

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

我已将其设为社区维基,因此请随时修改。

P.S。这是作业。

这是原始版本

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

8 个答案:

答案 0 :(得分:9)

首先,那些mallocs应该是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找不到它正在查找的分隔符,则strlen可以返回NULL,并且不能使用NULL调用malloc。如果没有足够的内存,strcpy可以返回NULL,并且您也无法使用NULL调用{{1}}。

答案 1 :(得分:2)

每个人都忽视的一件事是你在存储在const内存中的数组上调用strtok。 strtok写入你传递它的数组,所以确保你在调用strtok之前将它复制到临时数组,或者只是分配原来的数组:

char mybaz[] = "hello:world";

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

是的,那里的问题很少。

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

你已经走了更多功能但稍微困难的路线:你使用malloc()为你的结果创造空间(好到目前为止!)然后尝试将malloc'd空间分配给你传递的指针唉,唉,不行。

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

如果你那样,太好了。如果没有,请要求更多说明。

答案 5 :(得分:0)

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

此外,您只传入指针。由于指针是32位值(在32位机器上),您只需将整数指针的值传递给“fn”。以同样的方式,你不会表达一个传递给函数的整数返回给调用函数(没有显式地返回它),你不能指望一个指针做同样的事情。因此新的指针值永远不会返回到main函数。通常通过将指针传递给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 );
}

答案 6 :(得分:0)

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

但仍需要使用char * vs char **修复业务。没有办法解决这个问题。

答案 7 :(得分:0)

基本问题是虽然存储器曾经为malloc()myfoo尝试返回的结果进行了分配(mybar),但实际上并没有指向这些分配的指针返回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声明为初始化数组,而不是指向{{1}}的指针。初始化的数组将位于可写内存中,而字符串常量可能位于只读内存中。在许多情况下,字符串常量存储在用于保存可执行代码本身的内存的相同部分中,而现代系统都试图使程序难以修改其自己的运行代码。

在我工作的嵌入式系统中,代码很可能存储在某种ROM中,无法进行物理修改。