在C语言中返回字符串的正确方法

4
我有以下代码:

我有以下代码:

char* get_address_string(PACKAGE* pkg){
    char *c;
    sprintf(c, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], 
        pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return c;
}

代码运行良好。然而,我知道这不是在C中返回字符串的正确方法。我收到了警告“c在此函数中未初始化使用”。

在C中编写此函数的正确方式是什么?


首先,它绝对不会崩溃。然而,将输出缓冲区作为输入应该可以满足我的需求。 - mstagg
如果它不崩溃...相信我们,那只是偶然。从未听说过鼻子里飞出恶魔 - Adriano Repetti
@AdrianoRepetti:“它必须崩溃……”。不,这就是_未定义行为_的主要问题:任何事情都可能发生。最坏的情况是什么也不会出现。 - too honest for this site
@AdrianoRepetti:在调试中,变量不会自动清零。但是编译器应该在使用未初始化的变量时发出警告。这就是为什么我告诉我的学生首先启用所有(实际上大多数)警告的原因。 - too honest for this site
@Olaf 我同意始终启用所有警告。在调试版本中,MSVC将它们初始化为“魔术数字”,以帮助调试。 - Adriano Repetti
显示剩余4条评论
4个回答

9
在C语言中,“正确返回字符串”的方法并不存在。在C语言中,一个“字符串”是一个字符数组(包括空字符),并且数组本身不能从函数中返回。
函数可以返回指针。因此,“返回字符串”的常规方法是:
1. 返回指针。例如`char *strdup()`,函数声明为`char *foo1(...)`。 2. 传递指向字符数组的指针并修改其内容。例如`int sprintf(char *dest, const char *format, ...)`,函数声明为`void foo2(char *,...)`。 3. 结合1和2,例如`char *strcpy(char *dest, char *src)`,函数声明为`char *foo3(char *, ...)`。 4. 传递指针的地址并进行更新。例如`ssize_t getline(char **lineptr, size_t *n, FILE *stream)`,函数声明为`foo4(char **ptr)`。
关键是指针相关联的内存必须在函数完成后保持有效。返回指向函数非静态内存的指针是未定义的行为。成功的方法包括调用代码传递指针,或函数通过分配内存为某些持久值(如全局变量或字符串常量)提供指针。
当前设计惯例鼓励像上述2和3那样的函数还提供`size_t size`,以便函数了解可用内存的限制。
    char *foo2(char *s, size_t size, const pkg_T *pkg) {
      int result = snprintf(s, size, "%02x:%02x:%02x:%02x:%02x:%02x", 
        pkg->address[0], pkg->address[1], pkg->address[2], 
        pkg->address[3], pkg->address[4], pkg->address[5]);
      // encoding error or not enough room
      if (result < 0 || result >= size) return NULL;
      return s;
    }

另一种方法是分配内存(我倾向于使用上述方法)。这需要调用代码使用free()释放内存。
    #define UINT_MAX_WIDTH (sizeof(unsigned)*CHAR_BIT/3 + 3)

    char *foo2alloc(char *s, size_t size, const pkg_T *pkg) {
      char buf[(UINT_MAX_WIDTH+3)*6 + 1];
      int result = snprintf(buf, sizeof buf, "%02x:%02x:%02x:%02x:%02x:%02x", 
        pkg->address[0], pkg->address[1], pkg->address[2], 
        pkg->address[3], pkg->address[4], pkg->address[5]);
      // encoding error or not enough room
      if (result < 0 || result >= size) return NULL;
      return strdup(buf);
    }

我认为在这种情况下,在函数内分配内存有一定的优势,因为OP显然意图使字符串长度保持不变。 - user3079266
@Mints97 调用代码或此函数是否分配内存通常是一种更高级别的编码策略 - 两者都有优点。对于您的“函数内部”偏好,让此函数处理它具有应对通常烦人的问题的优势,例如打印 char address[6]; address[0] = 255 并获得意外的 "FFFFFFFF",而应该是 unsigned char address[6];"%02hhx:" - chux - Reinstate Monica

3

c是一个指针,但没有分配内存。返回值是可以的,在C语言中就是这样实现的。

但你需要分配内存。


2
如果c是像数组一样的局部变量,例如char [128],则返回值不正确。 - Giorgi Moniava
@Giorgi 不太确定我是否理解了。您能详细说明一下吗?举个例子会更有帮助。 - Ely
2
你写了 "返回值没问题" - 如果像这样声明 char c [128] ="test";,那就不行了。 - Giorgi Moniava
1
我们不知道OP如何使用它。我希望他能正确使用它。有很多如果存在。为什么要这么反生产力呢?在给定的上下文中,我认为这是可以的。 - Ely

0

由于 c 未初始化,sprintf 写入未知的内存位置,导致未指定的行为。它可能会立即崩溃,也可能根本不会崩溃,或者在某些完全无关的代码行上崩溃。

您需要通过使用 malloc 为指针分配内存来初始化它。

char* get_address_string(PACKAGE* pkg){
    char *c = malloc(20);  // enough room for output as 00:11:22:33:44:55 plus null terminator
    if (c == null) {
        perror("malloc failed");
        exit(1);
    }
    sprintf(c, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return c;
}

请注意,即使您事先知道需要多少内存,也不能通过数组在编译时设置它。以下是错误的做法:
char* get_address_string(PACKAGE* pkg){
    char c[20];    // allocated on the stack, contents unspecified on return
    sprintf(c, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return c;
}

翻译成中文是这样的:

char* get_address_string(PACKAGE* pkg){
    char c[20];    // allocated on the stack, contents unspecified on return
    char *p = c;
    sprintf(p, "%02x:%02x:%02x:%02x:%02x:%02x", pkg->address[0], pkg->address[1], pkg->address[2], pkg->address[3], pkg->address[4], pkg->address[5]);
    return p;
}

由于c在堆栈上分配,当get_address_string返回时,其内容是未指定的,从而再次导致未指定的行为。


-3

我更喜欢从调用者那里分配堆,这样清楚应该由谁释放它。

#include <stdio.h>
#include <malloc.h>

bool GetString(char ** retString, size_t size)
{
    // use size to do range check
    sprintf_s(*retString, size, "blah blah blah");

    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    size_t size = 100;
    char *data = (char *)malloc(size);

    if (data)
    {
        GetString(&data, size);

        free(data);
    }
    return 0;
}

2
为什么要使用双指针?传递一个简单的char *就足够了。 - alk
你展示的代码不是标准的C语言。在C语言中没有_tmain()这样的函数。顺便说一下,我没有给你点踩。 - alk
1
返回类型为bool且始终返回true的意义是什么? - Ed Heal
你只需要返回翻译后的文本内容,不要进行解释。如果方法失败或需要传递状态,则返回选项始终是一个好选择。 Translated text: 如果方法失败或需要传递状态,则返回选项始终是一个好选择。 - Kenny Lim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接