Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is stopping the compiler from eliminating data members unused in each particular context?

Is there any case where functions foo() and optimized_foo() in the example below are not equivalent?

struct Test
{
    int     x;
    int     y;
    int     z;
};

// Some external function. Implementation unknown.
void bar(int& arg);

void foo()
{
    Test      t;
    t.x = 3;
    t.y = 4;
    t.z = 5;
    bar(t.y);
}

void optimized_foo()
{
    int       t_y = 4;
    bar(t_y);
}

It's just that all major x86_64 compilers (gcc 10.2, clang 10.0, msvc 19.24) keep the initialization of t.x and t.z in assembler code generated for foo() even at the highest optimization level. Even though those members are obviously not used. Do they have a reason?

Am I right assuming that bar(), being given a reference to one data member of a structure, has no legal way to obtain a pointer/reference to other members? What does the standard say about it?

like image 577
Igor G Avatar asked Jul 31 '20 15:07

Igor G


1 Answers

bar could take the address of the the member through the reference [expr.unary.op]. The function could then copy the bytes of the object representation of the adjacent members.

void bar(int& arg) {
    constexpr auto size      = sizeof(Test);
    constexpr auto offset    = offsetof(Test, y);
    constexpr auto remaining = size - offset;
    unsigned char buffer[remaining];
    
    std::memcpy(buffer, &arg, remaining);
}

At the end of the function, the buffer contains object representation of some members of Test object. Given that bar is defined externally, the compiler could have no way of knowing whether the memory of the other members is observed or not when compiling foo.

Note: offsetof is only conditionally supported for non-standard-layout types. The class in question is standard layout.

[basic.types]

For any object (other than a potentially-overlapping subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes ([intro.memory]) making up the object can be copied into an array of char, unsigned char, or std​::​byte ([cstddef.syn]). If the content of that array is copied back into the object, the object shall subsequently hold its original value.

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). ...


P.S. I used the strange example observing only the successive members because observing the preceding members would require explicit pointer arithmetic which is somewhat ambiguously specified in the standard. I see no practical problems with doing that, but I left it out from the example to keep the issue separate. See related post.

like image 161
eerorika Avatar answered Nov 08 '22 23:11

eerorika