从函数返回C字符串


109

我正在尝试从函数返回C字符串,但是它不起作用。这是我的代码。

char myFunction()
{
    return "My String";
}

main我这样称呼它:

int main()
{
  printf("%s", myFunction());
}

我也尝试了其他方法myFunction,但是它们没有用。例如:

char myFunction()
{
  char array[] = "my string";
  return array;
}

注意:不允许使用指针!

关于这个问题的一点背景:

有功能可以找出是哪个月。例如,如果为1,则返回一月,依此类推。

因此,当要打印时,它是这样进行的:printf("Month: %s",calculateMonth(month));。现在的问题是如何从calculateMonth函数中返回该字符串。


10
不幸的是,在这种情况下,您需要指针。
尼克·贝德福德

1
@Hayato好吧,我相信我们已经在这里大人了,知道它应该返回0,这只是为了举例说明lox
。.– itsaboutcode

3
return 0默认情况下仅在C99(和C ++)中暗指,但在C90中不暗指。
hrnt

1
然后,您将无法做到这一点,除了笨拙的hack之外,无论如何,它们实际上只是分解了指针操作。存在指针是有原因的...:|
GManNickG

Answers:


222

您的功能签名必须为:

const char * myFunction()
{
    return "My String";
}

背景:

对于C&C ++来说,它是如此基础,但是应该进行更多的讨论。

在C语言中(字符串和C ++),字符串只是以零字节结尾的字节数组-因此,术语“字符串-零”用于表示字符串的这种特殊形式。还有其他种类的字符串,但是在C(&C ++)中,这种语言本身是语言固有的理解。其他语言(Java,Pascal等)使用不同的方法来理解“我的字符串”。

如果您曾经使用Windows API(C ++),则会经常看到类似“ LPCSTR lpszName”的函数参数。“ sz”部分表示“字符串零”的概念:带有空(/零)终止符的字节数组。

澄清:

为了这个“介绍”,我将“字节”和“字符”一词互换使用,因为这样更容易学习。请注意,还有其他方法(宽字符和多字节字符系统(mbcs))用于应对国际字符。UTF-8是mbcs的示例。为了介绍起见,我悄悄地“跳过”了所有这一切。

记忆:

这意味着像“我的字符串”之类的字符串实际上使用9 + 1(= 10!)个字节。重要的是要知道何时最终可以动态分配字符串。

因此,没有此“终止零”,您就没有字符串。您有一个字符数组(也称为缓冲区)在内存中徘徊。

数据寿命:

该函数的使用方式如下:

const char * myFunction()
{
    return "My String";
}

int main()
{
    const char* szSomeString = myFunction(); // Fraught with problems
    printf("%s", szSomeString);
}

...通常会使您陷入随机的,未处理的异常/段故障等,尤其是“下坡路”。

简而言之,尽管我的回答是正确的-如果以这种方式使用它,则十分之九会导致程序崩溃,尤其是如果您认为以这种方式进行操作是“好的习惯”的话。简而言之:通常不是。

例如,假设在将来的某个时间,现在需要以某种方式来操作字符串。通常,编码人员会“走简单的路”并(尝试)编写如下代码:

const char * myFunction(const char* name)
{
    char szBuffer[255];
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
    return szBuffer;
}

也就是说,你的程序会崩溃,因为编译器(可以/不可以)已经发布了使用的内存szBuffer由时间printf()main()被调用。(您的编译器也应事先警告您此类问题。)

有两种返回字符串的方法,这些字符串不会那么容易发怒。

  1. 返回存在一段时间的缓冲区(静态或动态分配)。在C ++中,使用“帮助程序类”(例如std::string)来处理数据的寿命(这需要更改函数的返回值),或者
  2. 将缓冲区传递给填充了信息的函数。

注意,如果不使用C中的指针,就不可能使用字符串。正如我所展示的,它们是同义词。即使在具有模板类的C ++中,在后台始终会使用缓冲区(即指针)。

因此,为了更好地回答(现在已修改的问题)。(肯定会提供各种“其他答案”。)

安全答案:

示例1,使用静态分配的字符串:

const char* calculateMonth(int month)
{
    static char* months[] = {"Jan", "Feb", "Mar" .... };
    static char badFood[] = "Unknown";
    if (month<1 || month>12)
        return badFood; // Choose whatever is appropriate for bad input. Crashing is never appropriate however.
    else
        return months[month-1];
}

int main()
{
    printf("%s", calculateMonth(2)); // Prints "Feb"
}

这里的“静态”功能(许多程序员不喜欢这种类型的“分配”)是将字符串放入程序的数据段中。也就是说,它是永久分配的。

如果转到C ++,则将使用类似的策略:

class Foo
{
    char _someData[12];
public:
    const char* someFunction() const
    { // The final 'const' is to let the compiler know that nothing is changed in the class when this function is called.
        return _someData;
    }
}

...,但是std::string如果您要编写自己使用的代码(而不是要与他人共享的库的一部分),则使用帮助器类(例如)可能会更容易。

示例2,使用调用方定义的缓冲区:

这是传递字符串的更“简单”的方法。返回的数据不受主叫方的操纵。也就是说,示例1容易被主叫方滥用,并使您面临应用程序故障。这样,它就更安全(尽管使用更多的代码行):

void calculateMonth(int month, char* pszMonth, int buffersize)
{
    const char* months[] = {"Jan", "Feb", "Mar" .... }; // Allocated dynamically during the function call. (Can be inefficient with a bad compiler)
    if (!pszMonth || buffersize<1)
        return; // Bad input. Let junk deal with junk data.
    if (month<1 || month>12)
    {
        *pszMonth = '\0'; // Return an 'empty' string
        // OR: strncpy(pszMonth, "Bad Month", buffersize-1);
    }
    else
    {
        strncpy(pszMonth, months[month-1], buffersize-1);
    }
    pszMonth[buffersize-1] = '\0'; // Ensure a valid terminating zero! Many people forget this!
}

int main()
{
    char month[16]; // 16 bytes allocated here on the stack.
    calculateMonth(3, month, sizeof(month));
    printf("%s", month); // Prints "Mar"
}

第二种方法更好的原因有很多,特别是如果您要编写供其他人使用的库(您无需锁定特定的分配/取消分配方案,则第三方无法破坏您的代码,并且您不需要链接到特定的内存管理库),但是像所有代码一样,它取决于您最喜欢的代码。出于这个原因,大多数人选择1作为示例,直到他们被烧了很多遍,以至于他们不再以这种方式编写它;)

免责声明:

我几年前退休了,现在我的C有点生锈了。此演示代码都应使用C正确编译(尽管对于任何C ++编译器都可以)。


2
实际上,该函数需要返回a char *,因为C中的字符串文字是type char[]。但是,不得以任何方式对其进行修改,因此const char*最好使用返回值(请参阅securecoding.cert.org/confluence/x/mwAV)。char *如果该字符串将用于(不幸的)期望使用char*as参数的旧版或外部库函数中,则可能需要返回该字符串,即使很难从该字符串中读取该字符串也是如此。另一方面,C ++具有const char[]类型的字符串文字(并且,自C ++ 11起,您也可以具有std::string文字)。
TManhente 2014年

17
我的前缀@cmroanirgo 向读者宣告该功能是由用户创建的。我发现在这种情况下使用完全合理。
2014年

4
根据这里:stackoverflow.com/questions/9970295/…,您可以返回字符串文字
giorgim 2015年

6
fraught with problems在“数据寿命”部分中标记的代码实际上是完全有效的。字符串文字在C / C ++中具有静态生存期。参见上面Giorgi提到的链接。
chengiz

1
@cmroanirgo返回字符串文字是一种很好的做法,也是一种好的样式。它不是“充满问题”,而且不会崩溃10次中的9次:它将永远不会崩溃。即使是80年代的编译器(至少是我使用过的编译器)也正确地支持字符串文字的无限生存期。注意:我不确定您对答案的解释是什么:我仍然看到它说它很容易崩溃。
塞茨

12

AC字符串定义为指向字符数组的指针。

如果不能有指针,那么根据定义就不能有字符串。


您可以将数组传递给函数,然后对该数组进行操作:void foo( char array[], int length)。当然,它array是一个隐藏的指针,但它不是“显式”的指针,因此对于那些学习数组但还没有完全学习过指针的人来说可能更直观。
jvriesem

12

请注意此新功能:

const char* myFunction()
{
    static char array[] = "my string";
    return array;
}

我将“数组”定义为静态。否则,当函数结束时,变量(以及您要返回的指针)将超出范围。由于该内存是在堆栈上分配的,因此它将损坏。该实现的缺点是代码不可重入且也不是线程安全的。

另一种选择是使用malloc在堆中分配字符串,然后在代码的正确位置释放该字符串。此代码将是可重入的和线程安全的。

如评论中所述,这是一个非常不好的做法,因为攻击者随后可以将代码注入到您的应用程序中(他/她需要使用GDB打开代码,然后创建一个断点并修改返回变量的值以使溢出和乐趣才刚刚开始)。

强烈建议让调用方处理有关内存分配的问题。参见以下新示例:

char* myFunction(char* output_str, size_t max_len)
{
   const char *str = "my string";
   size_t l = strlen(str);
   if (l+1 > max_len) {
      return NULL;
   }
   strcpy(str, str, l);
   return input;
}

请注意,唯一可以修改的内容就是用户自己可以修改的内容。另一个副作用-至少从库的角度来看,此代码现在是线程安全的。调用此方法的程序员应验证所使用的内存部分是线程安全的。


2
通常,这是处理问题的不好方法。char *可以被周围的代码操纵。也就是说,您可以执行以下操作:strcpy(myFunction(),“一个很长的字符串”); 并且您的程序将由于访问冲突而崩溃。
cmroanirgo 2014年

“那个用户”附近丢失了一些东西。
Peter Mortensen

8

您的问题出在函数的返回类型上-它必须是:

char *myFunction()

...然后您的原始公式将起作用。

请注意,在涉及指针的情况下,您不能在C语言字符串中使用C语言字符串。

另外:打开编译器警告。它应该警告您有关返回行将a转换char *char没有显式强制转换的信息。


1
我认为签名应该是const char *,因为字符串是文字,但是如果我没记错的话,编译器会接受。
卢克

5

基于您新添加的带问题的背景故事,为什么不只为月份返回1到12之间的整数,然后让main()函数使用switch语句或if-else阶梯决定打印什么?当然,这不是最好的方法-char *会-但是在这样的类中,我想这可能是最优雅的。


3

您可以在作为主函数的调用方中创建数组,并将该数组传递给作为myFunction()的被调用方。因此,myFunction可以将字符串填充到数组中。但是,您需要将myFunction()声明为

char* myFunction(char * buf, int buf_len){
  strncpy(buf, "my string", buf_len);
  return buf;
}

在主函数中,myFunction应该以这种方式调用:

char array[51];
memset(array, 0, 51); /* All bytes are set to '\0' */
printf("%s", myFunction(array, 50)); /* The buf_len argument  is 50, not 51. This is to make sure the string in buf is always null-terminated (array[50] is always '\0') */

但是,仍然使用指针。


2

您的函数返回类型是单个字符(char)。您应该返回一个指向字符数组第一个元素的指针。如果您不能使用指针,那么您将陷入困境。:(


2

还是这个呢?

void print_month(int month)
{
    switch (month)
    {
        case 0:
            printf("January");
            break;
        case 1:
            printf("february");
            break;
        ...etc...
    }
}

并将其与您在其他地方计算的月份一起调用。


1
+1不是OP的要求,但这可能是作业希望您执行的操作,因为他不能使用指针。
Vitim.us 2013年

甚至printf也使用指针。指针就像一把刀,对于生活和工作至关重要,但是您必须握住手柄,并用锋利的一面进行切割,否则您将度过一个糟糕的时光。对于许多新的C程序员来说,不幸的是在函数定义中放置空格是一个大脑错误。char * func(char * s); char func(char * s); char func * char * s); 都是一样的,但是看起来都不同,并且更令人困惑,*还是指针变量的取消引用运算符。
克里斯·里德

1

A char只是一个单字节字符。它不能存储字符串,也不可以是指针(显然您不能拥有)。因此,如果不使用指针(这char[]是语法糖),就无法解决问题。


1

如果您真的不能使用指针,请执行以下操作:

char get_string_char(int index)
{
    static char array[] = "my string";
    return array[index];
}

int main()
{
    for (int i = 0; i < 9; ++i)
        printf("%c", get_string_char(i));
    printf("\n");
    return 0;
}

魔术数字9太糟糕了,这不是良好编程的示例。但是你明白了。注意,指针和数组是同一类(有点),因此有点作弊。


通常,如果您需要对作业问题实施此类解决方案,那么您的初步假设是错误的。
hrnt

1

好吧,在您的代码中,您试图返回一个String(在C中,它只是一个以空终止的字符数组),但是函数的返回类型给您char带来了所有麻烦。相反,您应该这样写:

const char* myFunction()
{

    return "My String";

}

const在将C中的文字分配给指针时,最好使用类型来限定您的类型,因为C中的文字不可修改。


0

您的函数原型声明您的函数将返回一个char。因此,您不能在函数中返回字符串。



0

从函数返回字符串

#include <stdio.h>

const char* greet() {
  return "Hello";
}

int main(void) {
  printf("%s", greet());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.