从函数返回C字符串

its*_*ode 98 c

我试图从函数返回一个C字符串,但它不起作用.这是我的代码.

char myFunction()
{
    return "My String";
}
Run Code Online (Sandbox Code Playgroud)

在主要我称之为:

int main()
{
  printf("%s",myFunction());
}
Run Code Online (Sandbox Code Playgroud)

我也尝试了一些其他的方法,myFunction但他们没有工作.例如:

char myFunction()
{
  char array[] = "my string";
  return array;
}
Run Code Online (Sandbox Code Playgroud)

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

关于这个问题的小背景:有一个功能是找出它是哪个月; 如果它1然后它返回1月等等

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

cmr*_*rgo 202

你的功能签名需要是:

const char * myFunction()
{
    return "My String";
}
Run Code Online (Sandbox Code Playgroud)

编辑:

背景:

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

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

如果你曾经使用Windows API(使用C++),你会经常看到如下函数参数:"LPCSTR lpszName".'sz'部分代表了'string-zero'的概念:一个带有null(/ zero)终结符的字节数组.

澄清:

为了这个"介绍",我可以互换地使用"字节"和"字符"这个词,因为这种方式更容易学习.请注意,还有其他方法(宽字符和多字节字符系统 - 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);
}
Run Code Online (Sandbox Code Playgroud)

...通常会给你带来随机的未处理 - 例外/段故障等,特别是"在路上".

简而言之,虽然我的回答是正确的 - 如果你以这种方式使用它,你最终会得到一个崩溃的程序,特别是如果你认为以这种方式做到这一点是"好习惯"的话.简而言之:通常不是.

例如,想象一下将来的某个时间,现在需要以某种方式操纵字符串.通常,编码器将"采取简单的路径"并(尝试)编写如下代码:

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

也就是说,程序将崩溃,因为编译器(可能/可能没有)已经释放szBufferprintf()main()调用in 时使用的内存.(您的编译器也应事先警告您这些问题).

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

  1. 返回生存一段时间的缓冲区(静态或动态分配).在C++中使用'helper classes'(例如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"
}
Run Code Online (Sandbox Code Playgroud)

'静态'在这里做什么(许多程序员不喜欢这种类型的'分配')是字符串被放入程序的数据段.也就是说,它是永久分配的.

如果你转移到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;
    }   
}
Run Code Online (Sandbox Code Playgroud)

...但是使用帮助程序类可能更容易,例如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"
}
Run Code Online (Sandbox Code Playgroud)

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

免责声明:

几年前我退休了,我的C现在有点生疏了.这个演示代码应该全部用C编译(但对于任何C++编译器都可以).

  • @cmroanirgo*my*前缀向读者声明该函数是由用户创建的.我觉得在这样的环境中使用是完全合理的. (16认同)
  • 根据这里:http://stackoverflow.com/questions/9970295/life-time-of-string-literal-in-c,你可以返回字符串文字 (4认同)
  • 在"数据的长寿"部分标记为"充满问题"的代码实际上是完全有效的.字符串文字在C/C++中具有静态生命周期.请参阅Giorgi上面提到的链接. (4认同)
  • 实际上,函数需要返回一个`char*`,因为C中的字符串文字的类型是`char []`.但是,它们不得以任何方式进行修改,因此首选返回`const char*`(请参阅https://www.securecoding.cert.org/confluence/x/mwAV).如果字符串将在遗留或外部库函数中使用,那么可能需要返回`char*`,遗憾的是,这个函数需要`char*`作为参数,甚至很难只读取它.另一方面,C++具有`const char []`类型的字符串文字(并且,从C++ 11开始,你也可以使用`std :: string`文字). (2认同)

Cra*_*rks 12

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

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

  • 您可以将数组传递给函数,然后对该数组进行操作:“void foo( char array[], int length)”。当然,“数组”实际上是一个指针,但它并不是“明确”的指针,因此对于学习数组但“还没有完全”学习过指针的人来说,它可能更直观。 (2认同)

elc*_*uco 9

注意这个新功能:

const char* myFunction()
{
        static char array[] = "my string";
        return array;
}
Run Code Online (Sandbox Code Playgroud)

我将"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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

caf*_*caf 8

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

char *myFunction()
Run Code Online (Sandbox Code Playgroud)

...然后你的原始配方将起作用.

请注意,在行的某个位置,您不能使用没有指针的C字符串.

另外:打开你的编译器警告,它应该警告你关于转换char *char没有显式转换的返回线.

  • 我认为签名应该是 const char* 因为字符串是一个文字,但如果我没有记错的话,编译器会接受它。 (2认同)

Twi*_*sol 5

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