dimanche 25 juin 2023

When does the default copy assignment operator from C11 utilize bit-wise copy instead of member-wise copy?

In both x86-64 GCC 13.1 and Clang 16.0.0, the copy<PrivateBase> function uses member-wise copy, while the copy<PublicBase> function uses bit-wise copy. You could refer to the detailed source code and assembly code on the compiler explorer or see the code snippets provided below:

class PublicBase {
public:
    int num;
    char c1;
};

class PrivateBase {
private:
    int num;
    char c1;
};


template<typename T>
__attribute_noinline__ void copy(T *dst, T *src) {
    *dst = *src;
}

template void copy(PublicBase *dst, PublicBase *src);
template void copy(PrivateBase *dst, PrivateBase *src);
void copy<PublicBase>(PublicBase*, PublicBase*):
        mov     rax, QWORD PTR [rsi]
        mov     QWORD PTR [rdi], rax
        ret
void copy<PrivateBase>(PrivateBase*, PrivateBase*):
        mov     eax, DWORD PTR [rsi]
        mov     DWORD PTR [rdi], eax
        movzx   eax, BYTE PTR [rsi+4]
        mov     BYTE PTR [rdi+4], al
        ret

The question is, when does the default copy assignment operator from C++11 use bit-wise copy instead of member-wise copy? It seems that neither is_trivially_copyable nor is_pod provides the answer.

is_trivially_copyable

According to cppreference-is_trivially_copyable:

Objects of trivially-copyable types that are not potentially-overlapping subobjects are the only C++ objects that may be safely copied with std::memcpy.

Both PublicBase and PrivateBase are trivially copyable and not subobjects, but PrivateBase is copied with member-wise instead of bit-wise.

is_pod

If there is a derived class of PublicBase or PrivateBase, the derived class of PrivateBase will reuse the padding of the base class, while that of PublicBase won't.

Therefore, it is reasonable that PrivateBase is copied with member-wise. Otherwise, the padding of base class may overwrite PrivateDerived::c2 when calling copy<PrivateBase>(derived, base).


class PublicDerived : public PublicBase {
public:
    char c2;
};

class PrivateDerived : public PrivateBase {
private:
    char c2;
};


int main() {
    std::cout << "sizeof(PublicBase)=" << sizeof(PublicBase) << std::endl;
    std::cout << "sizeof(PublicDerived)=" << sizeof(PublicDerived) << std::endl;
    std::cout << "sizeof(PrivateBase)=" << sizeof(PrivateBase) << std::endl;
    std::cout << "sizeof(PrivateDerived)=" << sizeof(PrivateDerived) << std::endl;

    return 0;
}
// Output:
// sizeof(PublicBase)=8
// sizeof(PublicDerived)=12
// sizeof(PrivateBase)=8
// sizeof(PrivateDerived)=8

I am confused about how the compiler decides to reuse padding of the base class or not. According to the related question, the POD type doesn't reuse padding of the base class.

According to the cppreference-POD_class:

A POD class is a class that

  • until C++11:
    • is an aggregate (no private or protected non-static data members),
    • has no user-declared copy assignment operator,
    • has no user-declared destructor, and
    • has no non-static data members of type non-POD class (or array of such types) or reference.
  • since C++11
    • is a trivial class,
    • is a standard-layout class (has the same access control for all non-static data members), and
    • has no non-static data members of type non-POD class (or array of such types).

before C++11, PrivateBase is not POD type (because it has private data members), but since C++11, it becomes POD type (because it has the same access control for all non-static data members).


int main() {
    std::cout << "PublicBase: is_standard_layout=" << is_standard_layout<PublicBase>::value
              << ", is_trivial=" << is_trivial<PublicBase>::value
              << ", is_pod=" << is_pod<PublicBase>::value << std::endl;

    std::cout << "PrivateBase: is_standard_layout=" << is_standard_layout<PrivateBase>::value
              << ", is_trivial=" << is_trivial<PrivateBase>::value
              << ", is_pod=" << is_pod<PrivateBase>::value << std::endl;
}
// Output:
// PublicBase: is_standard_layout=1, is_trivial=1, is_pod=1
// PrivateBase: is_standard_layout=1, is_trivial=1, is_pod=1

Aucun commentaire:

Enregistrer un commentaire