【问题标题】:printing a member of a returned struct打印返回结构的成员
【发布时间】:2011-12-19 07:30:54
【问题描述】:

我在打印从函数返回的结构成员时遇到问题:

#include <stdio.h>

struct hex_string
{
    char a[9];
};

struct hex_string to_hex_string_(unsigned x)
{
    static const char hex_digits[] = "0123456789ABCDEF";
    struct hex_string result;
    char * p = result.a;
    int i;
    for (i = 28; i >= 0; i -= 4)
    {
        *p++ = hex_digits[(x >> i) & 15];
    }
    *p = 0;
    printf("%s\n", result.a);   /* works */
    return result;
}

void test_hex(void)
{
    printf("%s\n", to_hex_string_(12345).a);   /* crashes */
}

to_hex_string_ 内部的printf 调用打印正确的结果,但test_hex 内部的printf 调用使我的程序崩溃。为什么会这样?这是一生的问题,还是其他问题?

当我将 printf 调用替换为 puts(to_hex_string_(12345).a) 时,出现编译器错误:

invalid use of non-lvalue array

这是怎么回事?

【问题讨论】:

  • @LuchianGrigore:这是未定义行为的一种可能症状。
  • 您的源文件顶部是否有#include &lt;stdio.h&gt;?为什么函数名后面有下划线? (这是合法的,但很奇怪。)
  • @KeithThompson:是的,包括stdio.h。如果我已经有一个同名的稳定函数,尾随下划线是我个人对一次性玩具函数的约定。
  • 我已将#include &lt;stdio.h&gt; 添加到问题中。在某些情况下(不是这个),缺少#include 指令可能是问题的原因。

标签: c arrays struct return-value lvalue


【解决方案1】:

您已经设法遇到了该语言的一个相当晦涩的极端情况。

在大多数情况下,数组类型的表达式被隐式转换为指向数组第一个元素的指针;例外情况是表达式是一元 &amp; 运算符的操作数,它是一元 sizeof 运算符的操作数,以及它是用于初始化数组对象的初始化程序中的字符串文字。这些例外都不适用于此处。

但是在这个转换中有一个隐含的假设:指针指向数组object的第一个元素。

大多数数组表达式——事实上几乎所有的——都引用了一些数组对象,例如声明的数组变量、多维数组的元素等等。函数不能返回数组,所以不能这样得到非左值数组表达式。

但正如您所见,函数可以返回一个包含数组的结构——并且没有与数组表达式 to_hex_string_(12345).a 关联的对象。

新的 ISO C11 标准通过在描述存储持续时间的部分中添加新的措辞来解决这个问题。 The N1570 draft,第 6.2.4p8 节说:

具有结构或联合类型的非左值表达式,其中 结构或联合包含具有数组类型的成员(包括, 递归地,所有包含的结构和联合的成员)指 具有自动存储期限和临时生命周期的对象。 它的生命周期从计算表达式及其初始值开始 value 是表达式的值。它的生命周期结束时 包含完整表达式或完整声明符的评估结束。 任何尝试修改具有临时生命周期的对象都会导致 未定义的行为。

实际上,这表示您的函数的返回值(与大多数函数结果不同)是一个临时对象的值,允许其数组成员的衰减给您一个(临时)有效的指针。

但在编译器完全支持新的 C 标准之前(几年后不会支持),您只需要避免引用返回结构的数组成员即可。

【讨论】:

  • 啊哈,所以 C 还没有 C++ 的临时对象概念。使它们成为“const”的有趣选择,在 C++ 中并非如此。
  • 那么为什么printf调用崩溃,而puts调用编译失败?
  • @FredOverflow:这很可能是一个 gcc 错误。使用 gcc 4.5.2,我在printf 调用中收到警告:warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘char[9]’(显然它没有进行通常的数组到指针的转换)。在puts 电话中,我得到error: invalid use of non-lvalue array,就像你一样。我想不出有什么好的理由让他们表现出不同的那个。但是行为是不确定的,所以你应该避免这样做。
  • @FredOverflow:它实际上不是“const”。试图修改它有未定义的行为,但它不是const-qualified。类似于 C 对字符串字面量的处理。
【解决方案2】:

C 中有一条很少生效的规则,它规定:

如果试图修改函数调用的结果或 在下一个序列点之后访问它,行为是未定义的。 (C99 §6.5.2.2)

在这种情况下,在评估 printf() 的参数之后和 printf() 函数本身执行之前有一个序列点。您传递给printf() 的指针是一个指向返回值本身的元素的指针 - 当printf() 尝试通过该指针访问字符串时,您就会崩溃。

这个问题很难遇到,因为函数值不是左值,所以你不能直接用&amp;指向它。

【讨论】:

  • 那么为什么printf调用崩溃,而puts调用编译失败?
  • @FredOverflow:gcc 行为的差异似乎是 puts() 的函数原型的结果(如果您尝试像 char *x = to_hex_string(1).a 这样的赋值,则会收到相同的警告)。我认为这是一个错误,因为只有在通过该指针值访问数组时才会出现未定义的行为;仅仅复制指针值似乎与任何约束都不矛盾。不过,这可能并不重要。
  • 我还注意到,如果您使用 &amp;to_hex_string(1).a[0],gcc 警告会消失,这凸显了该警告的脆弱性。
  • 我很好奇:我在 C99 §6.5.2.2 标准的原始版本中找不到这些词,在 3 个技术勘误中也找不到这些词。它在第 6.5.2.2 节的 C11 标准中也不存在。
  • 在 C11 中,§6.2.4 ¶8 说 具有结构或联合类型的非左值表达式,其中结构或联合包含具有数组类型的成员(递归地包括所有包含的结构和联合)指的是具有自动存储持续时间和临时生命周期的对象。36)它的生命周期从计算表达式时开始,其初始值是表达式的值。当包含完整表达式或完整声明符的评估结束时,它的生命周期结束。任何修改具有临时生命周期的对象的尝试都会导致未定义的行为。
【解决方案3】:

您面临的问题是:返回的变量result是函数_to_hex_string的局部变量,这意味着它在函数调用结束时被删除。因此,当您尝试在test_hex 函数中检查它时,它不再可用。

要解决您的问题,您可以处理指针。

这是你的代码修改

struct hex_string
{
    char a[9];
};

struct hex_string * to_hex_string_(unsigned x) // here you return a pointer
{
    static const char hex_digits[] = "0123456789ABCDEF";
    struct hex_string result;

    result = (struct hex_string *) malloc(sizeof(struct hex_string));
    char * p = result->a;
    int i;

    for (i = 28; i >= 0; i -= 4)
    {
        *p++ = hex_digits[(x >> i) & 15];
    }

    *p = 0;
    printf("%s\n", result->a);   /* works */
    return result;
}

void test_hex(void)
{
    printf("%s\n", to_hex_string_(12345)->a);  /* works */
}

今天过得愉快吗

【讨论】:

  • malloc,但你从来没有free。那是内存泄漏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
  • 2019-07-28
  • 2013-06-19
  • 1970-01-01
  • 2021-11-22
  • 2016-11-20
相关资源
最近更新 更多