从函数返回 C 字符串

我试图从一个函数返回一个 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函数返回该字符串。

368420 次浏览

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

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

char *myFunction()

然后你的原始配方就会起作用。

请注意,您的 不能有 C 字符串,其中不包括指针。

另外: 打开编译器警告。它应该提醒您,返回行在没有显式强制转换的情况下将 char *转换为 char

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

您的函数签名需要:

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

背景:

这对 C & C + + 来说是非常基础的,但是我们不需要再讨论什么了。

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

如果您曾经使用过 Windows API (在 C + + 中) ,您将看到非常规则的函数参数,如: “ LPCSTR lpszName”。‘ sz’部分表示‘ string-zero’的概念: 带有 null (/zero)结束符的字节数组。

澄清:

为了进行这个“介绍”,我将“字节”和“字符”交替使用,因为这样更容易学习。请注意,还有其他用于处理国际字符的方法(宽字符和多字节字符系统(MBC))。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);
}

... 通常会给你带来随机的未处理的异常/段错误等等,特别是“以后”。

简而言之,尽管我的答案是正确的——10次中有9次,如果你那样使用它,你最终会得到一个崩溃的程序,特别是如果你认为那样做是“好的练习”。简而言之: 通常不是这样的。

例如,假设将来某个时候,现在需要以某种方式操作字符串。一般来说,编码员会“选择简单的路径”,并且(试图)编写这样的代码:

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

也就是说,您的程序将会崩溃,因为在 main()中的 printf()被调用时,编译器(可能/可能不)已经释放了 szBuffer使用的内存。(你的编译器也应该事先警告你这样的问题。)

返回字符串有两种方法,它们不会那么容易呕吐。

  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"
}

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

如果你转移到 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;
}
}

... 但是如果您编写的代码是为了自己使用(而不是库的一部分,以便与其他人共享) ,那么使用 helper 类(如 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 + + 编译器来说都是可以的)。

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

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

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

注意这个新函数:

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

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

如果你真的不能使用指针,可以这样做:

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是可怕的,这不是一个好编程的例子。但你明白我的意思。请注意,指针和数组是一回事(类似) ,所以这有点欺骗。

根据您新添加的问题背景故事,为什么不返回一个1到12的整数,并让 main ()函数使用 switch 语句或 if-else 梯子来决定打印什么?这当然不是最好的方式去-char * 将是-但在这样一个类的上下文中,我想它可能是最优雅的。

或者这个怎么样:

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

把它和其他地方的月份一起计算。

在你的代码中,你试图返回一个 String(在 C 语言中,它只是一个以空结尾的字符数组) ,但是你函数的返回类型是 char,这给你带来了麻烦。相反,你应该这样写:

const char* myFunction()
{


return "My String";


}

const限定您的类型总是好的,而将 C 中的字面值赋给指针,因为 C 中的字面值是不可修改的。

您可以在调用方(main 函数)中创建数组,并将数组传递给被调用方(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)); /* 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') */

但是,仍然使用指针。

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

在 C 语言中,字符串文字是具有静态常量内存类的数组,因此返回指向该数组的指针是安全的。更多细节在堆栈溢出问题 < a href = “ https://stackoverflow. com/questions/9970295/Life-time-of-a-string-Literal-in-c”> C 中字符串文字的“ Life-time”

从函数返回字符串

#include <stdio.h>


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


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

另一件需要记住的事情是,不能从 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());
}