打印返回结构的成员

fre*_*low 16 c arrays struct return-value lvalue

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

#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 */
}
Run Code Online (Sandbox Code Playgroud)

printf呼叫内to_hex_string_打印出正确的结果,但printf调用内部test_hex崩溃我的程序.究竟是为什么呢?这是一生的问题,还是别的什么?

当我用这个替换printf调用时puts(to_hex_string_(12345).a),我得到一个编译器错误:

invalid use of non-lvalue array
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

caf*_*caf 18

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

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

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

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


Kei*_*son 13

你已经设法遇到一个相当模糊的语言角落案例.

在大多数情况下,数组类型的表达式被隐式转换为指向数组第一个元素的指针; 的例外是当表达是一元的操作数&操作,当它是一个一元的操作数sizeof操作者,并且当它在用于初始化一个数组对象的初始化文字的字符串.这些例外都不适用于此.

但是在转换中有一个隐含的假设:指针指向数组对象的第一个元素.

大多数数组表达式 - 实际上几乎所有数组 - 都引用了一些数组对象,例如声明的数组变量,多维数组的元素等等.函数不能返回数组,因此您无法以这种方式获得非左值数组表达式.

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

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

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

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

但是在编译器完全支持新的C标准(将不会存在多年)之前,您只需要避免引用返回结构的数组成员.

  • @FredOverflow:它实际上不是"const".尝试修改它有未定义的行为,但它不是'const`限定的.它类似于C对字符串文字的处理. (2认同)