Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

printing a member of a returned struct

I'm having trouble printing a member of a struct that is returned from a function:

#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 */
}

The printf call inside to_hex_string_ prints the correct result, but the printf call inside test_hex crashes my program. Why exactly is that? Is it a lifetime issue, or is it something else?

When I replace the printf call with puts(to_hex_string_(12345).a), I get a compiler error:

invalid use of non-lvalue array

What's going on here?

like image 492
fredoverflow Avatar asked Nov 01 '11 08:11

fredoverflow


2 Answers

There is a rule in C which seldom comes into effect, which states:

If an attempt is made to modify the result of a function call or to access it after the next sequence point, the behavior is undefined. (C99 §6.5.2.2)

In this case, there is a sequence point after the arguments to printf() are evaluated and before the printf() function itself executes. The pointer you pass to printf() is a pointer to an element of the return value itself - and when printf() tries to access the string through that pointer, you get your crash.

This issue is hard to run into, because a function value isn't an lvalue so you can't directly take a pointer to it with &.

like image 118
caf Avatar answered Oct 16 '22 04:10

caf


You've managed to run into a fairly obscure corner case of the language.

An expression of array type, in most contexts, is implicitly converted to a pointer to the first element of the array; the exceptions are when the expression is the operand of a unary & operator, when it's the operand of a unary sizeof operator, and when it's a string literal in an initializer used to initialize an array object. None of these exceptions applies here.

But there's an implicit assumption in that conversion: the pointer is to the first element of the array object.

Most array expressions -- almost all of them, in fact -- refer to some array object, such as a declared array variable, an element of a multidimensional array, and so forth. Functions can't return arrays, so you can't get a non-lvalue array expression that way.

But as you've seen, a function can return a struct that contains an array -- and there's no object associated with the array expression to_hex_string_(12345).a.

The new ISO C11 standard addresses this by adding new wording to the section describing storage durations. The N1570 draft, section 6.2.4p8, says:

A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime. Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression or full declarator ends. Any attempt to modify an object with temporary lifetime results in undefined behavior.

In effect, this says that the returned value from your function (unlike most function results) is the value of a temporary object, allowing the decay of its array member to give you a (temporarily) valid pointer.

But until compilers fully support the new C standard (which won't be for some years), you'll just have to avoid referring to array members of returned structures.

like image 35
Keith Thompson Avatar answered Oct 16 '22 03:10

Keith Thompson