从函数返回C字符串

时间:2009-09-30 05:59:46

标签: c

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

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

主要是我这样称呼:

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

我还尝试了myFunction的其他方法,但它们没有用。 E.g:

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

注意:我不允许使用指针!

这个问题的背景很少: 有找出哪个月的功能;如果它1然后它返回1月等等。

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

15 个答案:

答案 0 :(得分:182)

您的功能签名必须是:

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

修改

<强>背景

这篇文章已经有好几年了。从来没有想过它会被投票,因为它对C&amp; amp; C ++。尽管如此,还是应该进行更多的讨论。

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

如果您使用的是Windows API(使用的是C ++),您会看到非常有规律的函数参数,例如:“LPCSTR lpszName”。 'sz'部分代表了'string-zero'的概念:一个带有null(/ zero)终结符的字节数组。

<强>澄清:

为了这个'介绍',我可以互换地使用'bytes'和'characters'这个词,因为这样学习起来比较容易。请注意,还有其他方法(宽字符和多字节字符系统 - mbcs)用于处理国际字符。 UTF-8是mbcs的一个例子。为了介绍,我悄悄地“跳过”所有这些。

<强>内存

这意味着像“my string”这样的字符串实际上使用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()使用的内存。 (您的编译器也应事先警告您这些问题)。

有两种方法可以返回不会很容易barf的字符串。

  1. 返回生存一段时间的缓冲区(静态或动态分配)。在C ++中使用'helper classes'(例如std::string)来处理数据的寿命(需要更改函数的返回值),或者
  2. 将缓冲区传递给填入信息的函数。
  3. 请注意,如果不使用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 ++编译器都可以)。

答案 1 :(得分:12)

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

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

答案 2 :(得分:8)

注意这个新功能:

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

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

另一种方法是使用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;
}

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

答案 3 :(得分:7)

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

char *myFunction()

......然后你的原始配方就可以了。

请注意,不能在没有指针的情况下使用C字符串,就在某处。

另外:提出你的编译器警告,它应该警告你关于将char *转换为char而没有明确演员的返回行。

答案 4 :(得分:5)

根据您新添加的带有问题的背景故事,为什么不在本月返回1到12之间的整数,让main()函数使用switch语句或if-else梯形图来决定打印什么?它肯定不是最好的方式 - char *会 - 但在这样的类的上下文中,我想它可能是最优雅的。

答案 5 :(得分:3)

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

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

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

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

但是仍然使用指针。

答案 6 :(得分:2)

您的函数返回类型是一个char。您应该返回指向字符数组的第一个元素的指针。如果你不能使用指针,那你就搞砸了。 :(

答案 7 :(得分:2)

或者这个怎​​么样:

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

用你在其他地方计算的月份来调用它。

此致

Sebastiaan

答案 8 :(得分:1)

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

答案 9 :(得分: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很糟糕,这不是一个好的编程的例子。但你明白了。请注意,指针和数组是相同的(有点),所以这有点作弊。

希望这有帮助!

答案 10 :(得分:1)

在您的代码中,您正在尝试返回String(在C中,这只是空终止的字符数组),但函数的返回类型为char,这会导致给你带来的麻烦。相反,你应该这样写:

const char* myFunction()
{

    return "My String";

}

使用const对类型进行限定总是好的,同时将C中的文字指定为指针,因为C中的文字是不可修改的。

答案 11 :(得分:0)

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

答案 12 :(得分:0)

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

在C字符串中,文字是具有静态常量内存类的数组,因此返回指向该数组的指针是安全的。此处有更多详细信息:"life-time" of string literal in C

答案 13 :(得分:0)

从函数返回字符串

#include <stdio.h>

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

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

答案 14 :(得分:0)

要记住的另一件事是,您不能从 C 函数返回定义为局部变量的字符串,因为该变量会在函数执行完成后自动销毁(释放)。

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

char *myfunc(){
    char *myvar = (char *)malloc(20);
    printf("Plese enter some text \n");
    fgets(myvar, 20, stdin);
    return myvar;
}
int main(){
    printf("You entered: %s", myfunc());
}