C/C++ programming: objects

C++ programs can create, destroy, reference, access and manipulate objects

In C++, an object can have these properties

  • Size (can be used to sizeofobtain)
  • Alignment requirements (can be used to alignofobtain)
  • Storage period (automatic, static, dynamic, thread local);
  • Lifetime (bound with storage period or temporary)
  • Types of;
  • Value (may be indeterminate, such as non-class types initialized by default);
  • Name (optional).

None of the following entities are objects: values, references, functions, enumerations, types, non-static members of classes, templates, specializations of classes or function templates, namespaces, parameter packs, and this.

The variable is introduced by the declaration and is an object or not a reference to a non-static data member.

Object creation

Objects can be created by displaying the active members 定义、new 表达式、throw表达式, changing 联合体active members, and 临时对象expressions required for evaluation. Uniquely defined when the object created in the explicit object creation

吟诗生存期类型The object can also be implicitly created by the following operations

  • Start the operation of char, unsigned char or std::byte (since C++17) array lifetime, in this case, create such an object in the array,
  • The following allocation function is called, in which case such an object is created in the allocated storage:
    • operator new
    • operator new[]
    • std::malloc
    • std::calloc
    • std::realloc
    • std::aligned_alloc (since C++17)
  • Calling the following objects represents a copy function, in which case such an object is created in the target storage area or result:
    • std::memcpy
    • std::memmove
    • std::bit_cast (since C++20)
#include <cstdlib>
struct X {
    
     
    int a, b; 
};

X *MakeX()
{
    
    
    // 可能的有定义行为之一:
    // 调用 std::malloc 隐式创建一个 X 类型对象及其子对象 a 与 b ,并返回指向该 X 对象的指针
    X *p = static_cast<X*>(std::malloc(sizeof(X)));
    p->a = 1;
    p->b = 2;
    return p;
}

Object representation and value representation

  • For an object of type T, the ` object representation is an unsigned char (or equivalent std::byte) (since C++17) type starting at the same address as it and having a length of sizeof(T) Sequence of objects .
  • Target value indicates the value which is held by a type T set of bits .
  • For 可平凡赋值types, the value representation is part of the object representation, which means that copying the bytes occupied by the object in storage is enough to produce another object with the same value (unless the value is a "trap representation" of the type, Reading it into the CPU will generate a hardware exception, like the floating-point value of SNaN ("Signaling NaN") or the integer value of NaT ("Not a Thing")).
  • The reverse is not necessarily true: two objects of the TriviallyCopyable type with different object representations may exhibit the same value. For example, floating-point numbers have multiple bit patterns that all represent the same special value NaN. More commonly, some bits of the object representation may not participate in the value representation at all; these bits may be filled in order to meet the alignment requirements, and the size of the bit field is satisfied.
#include <cassert>
struct S {
    
    
    char c;  // 1 字节值
             // 3 字节填充(假设 alignof(float) == 4 )
    float f; // 4 字节值  (假设 sizeof(float) == 4 )
    bool operator==(const S& arg) const {
    
     // 基于值的相等
        return c == arg.c && f == arg.f;
    }
};
 
void f() {
    
    
    static_assert(sizeof(S) == 8);
    S s1 = {
    
    'a', 3.14};
    S s2 = s1;
    reinterpret_cast<unsigned char*>(&s1)[2] = 'b'; // 更改填充的第 2 字节
    assert(s1 == s2); // 值并未更改
}
  • For objects of type char, signed charand unsigned char, unless they are bit fields that are too large, each bit represented by the object participates in its value representation, and each bit pattern represents an independent value (no padding or trap bits, Multiple representations of values ​​are not allowed)

Child object

An object can have child objects. Child objects include

  • Member object
  • Base class sub-object
  • Array object

Objects that are not child objects of any other object are called complete objects

If the child is one of the following, it potentially overlaps

  • Base class child object, or
  • Declare non-static data members with [[no_unique_address]] attributes.
    (Since C++20)

Complete objects, member objects, and array objects are also called final derived objects , which are also separated from base class sub-objects. The size of objects that are neither potentially overlapping nor bit fields cannot be zero (the size of the base class sub-object may be zero, even if there is no [[no_unique_address]] (since C++20): see empty base class optimization

An object can contain other objects, in which case the contained object is embedded in the predecessor object. If the following conditions are met, the object a is embedded in another object b:

  • a is a child of b, or
  • b provides storage for a, or
  • There is an object c, where a is embedded in c and c is embedded in b.

Any two (non-bitfield) objects with overlapping lifetimes must have different addresses, unless one of the objects is embedded in the other, or both objects are sub-objects of different types in the same complete object , And one of them is a sub-object with a size of zero.

static const char c1 = 'x';
static const char c2 = 'x';
assert(&c1 != &c2); // 值相同,地址不同

Polymorphic object

An object of a class type that declares or inherits at least one virtual function is a polymorphic object

In each polymorphic object, the implementation stores additional information (in all existing implementations, if not optimized by the compiler, this is a pointer), which is used to call virtual functions, RTTI features ( dynamic_cast 和 typeid) It is also used to determine the type used when the object is created at runtime, regardless of the type of expression that uses it.

For non-polymorphic objects, the interpretation of the value is determined by the expression using the object, which is already determined by the compiler

#include <iostream>
#include <typeinfo>
struct Base1 {
    
    
    // 多态类型:声明了虚成员
    virtual ~Base1() {
    
    }
};
struct Derived1 : Base1 {
    
    
    // 多态类型:继承了虚成员
};
 
struct Base2 {
    
    
    // 非多态类型
};
struct Derived2 : Base2 {
    
    
    // 非多态类型
};
 
int main()
{
    
    
    Derived1 obj1; // object1 创建为类型 Derived1
    Derived2 obj2; // object2 创建为类型 Derived2
 
    Base1& b1 = obj1; // b1 指代对象 obj1
    Base2& b2 = obj2; // b2 指代对象 obj2
 
    std::cout << "b1 的表达式类型: " << typeid(decltype(b1)).name() << '\n'
              << "b2 的表达式类型: " << typeid(decltype(b2)).name() << '\n'
              << "b1 的对象类型: " << typeid(b1).name() << '\n'
              << "b2 的对象类型: " << typeid(b2).name() << '\n'
              << "b1 的大小: " << sizeof b1 << '\n'
              << "b2 的大小: " << sizeof b2 << '\n';
}

Insert picture description here

Align

  • Each object type has a property called alignment requirement , which is an integer (type std::size_t, always a power of 2), which means that different objects of this type can be allocated between consecutive adjacent addresses Number of bytes
  • (Since C++11) You can use alignofor std::alignment_ofto query the requirements of the type. Pointer alignment function may be used std::alignto obtain a suitable alignment buffer pointer passes,
  • Each object type enforces the alignment requirements of that type on all objects of that type, (since C++11), you can use alignas to require stricter alignment (larger alignment requirements).
  • In order to make all non-static members in the class meet the alignment requirements, some padding will be inserted after some members.
#include <iostream>
 
// S 类型的对象可以在任何地址上分配
// 因为 S.a 和 S.b 都可以在任何地址上分配
struct S {
    
    
  char a; // 大小:1,对齐:1
  char b; // 大小:1,对齐:1
}; // 大小:2,对齐:1
 
// X 类型的对象只能在 4 字节边界上分配
// 因为 X.n 必须在 4 字节边界上分配
// 因为 int 的对齐要求(通常)就是 4
struct X {
    
    
  int n;  // 大小:4,对齐:4
  char c; // 大小:1,对齐:1
  // 三个填充字节
}; // 大小:8,对齐:4
 
int main()
{
    
    
    std::cout << "sizeof(S) = " << sizeof(S)
              << " alignof(S) = " << alignof(S) << '\n';
    std::cout << "sizeof(X) = " << sizeof(X)
              << " alignof(X) = " << alignof(X) << '\n';
}

Insert picture description here
Align the weakest (minimum requirement alignment) are char、signed char 和 unsigned charaligned, equal to 1; all aligned largest base types (fundamental alignment) is implementation-defined, equal and std::max_align_taligned. (Since C++11)

When alignas is used to make the alignment of a certain type stricter (larger) than std::max_align_t, it is called a type with extended alignment requirements.

  • Types with extended alignment or class types that contain non-static members with extended alignment are called 过对齐types.
  • Whether new expressions (before C++17) std::allocator::allocate and std::get_temporary_buffer (before C++20) support over-aligned types is implementation-defined.
  • Allocators instantiated with over-aligned types allow instantiation failures at compile time, throw std::bad_alloc exceptions at runtime, silently ignore unsupported alignment requirements, and allow them to be handled correctly

alignof

effect

  • Alignment requirements for query types

grammar

  • alignof( 类型标识 )

return value:

  • Returns a value of type std::size_t.

Explanation:

  • Returns the number of alignment bytes required by any instance of the type indicated by the type identifier . The type can be a complete object type, an array type with a complete element type, or a reference type to one of these types.
  • If the type is a reference type, the operator returns the alignment of the referenced type;
  • If the type is an array type, the alignment requirements of the element type are returned.
#include <iostream>
 
struct Foo {
    
    
    int   i;
    float f;
    char  c;
};
 
// 注:下面的 `alignas(alignof(long double))` 如果需要可以简化为 
// `alignas(long double)`
struct alignas(alignof(long double)) Foo2 {
    
    
    // Foo2 成员的定义...
};
 
struct Empty {
    
    };
 
struct alignas(64) Empty64 {
    
    };
 
int main()
{
    
    
    std::cout << "对齐字节数"  "\n"
        "- char             :" << alignof(char)    << "\n"
        "- 指针             :" << alignof(int*)    << "\n"
        "- Foo 类           :" << alignof(Foo)     << "\n"
        "- Foo2 类          :" << alignof(Foo2)     << "\n"
        "- 空类             :" << alignof(Empty)   << "\n"
        "- alignas(64) Empty:" << alignof(Empty64) << "\n";
}

Insert picture description here

std::alignment_of

head File

  • <type_traits>

prototype

template< class T >
struct alignment_of;

effect:

  • And alignofthe same
#include <iostream>
#include <type_traits>
 
class A {
    
    };
 
int main() 
{
    
    
    std::cout << std::alignment_of<A>::value << '\n';
    std::cout << std::alignment_of<int>() << '\n'; // 另一种语法
    std::cout << std::alignment_of_v<double> << '\n'; // c++17 另一种语法
}

Insert picture description here

std::align

head File

  • <memory>

prototype

void* align( std::size_t alignment,
            std::size_t size,
            void*& ptr,
            std::size_t& space );

parameter:

  • alignment -The desired amount of alignment
  • size -The size of the storage to be aligned
  • ptr-Pointer to contiguous storage with at least space bytes-
    spaceThe size of the buffer to operate in

return value:

  • The adjusted value of ptr, or a null pointer value if the space provided is too small.

effect:

  • Given a pointer ptr specifies a buffer with a size of space, return a pointer aligned with the specified alignment as the number of bytes in size, and reduce the number of bytes used for the alignment of the space parameter. Returns the first aligned address.
  • The function will modify the pointer only if the number of bytes required to align into the buffer with the given alignment is appropriate. If the buffer is too small, the function does nothing and returns nullptr
  • If alignment is not a power of two, the behavior is undefined.

Official document: https://zh.cppreference.com/w/cpp/memory/align

#include <iostream>
#include <memory>
 
template <std::size_t N>
struct MyAllocator
{
    
    
    char data[N];
    void* p;
    std::size_t sz;
    MyAllocator() : p(data), sz(N) {
    
    }
    template <typename T>
    T* aligned_alloc(std::size_t a = alignof(T))
    {
    
    
        if (std::align(a, sizeof(T), p, sz))
        {
    
    
            T* result = reinterpret_cast<T*>(p);
            p = (char*)p + sizeof(T);
            sz -= sizeof(T);
            return result;
        }
        return nullptr;
    }
};
 
int main()
{
    
    
    MyAllocator<64> a;
 
    // 分配一个 char
    char* p1 = a.aligned_alloc<char>();
    if (p1)
        *p1 = 'a';
    std::cout << "allocated a char at " << (void*)p1 << '\n';
 
    // 分配一个 int
    int* p2 = a.aligned_alloc<int>();
    if (p2)
        *p2 = 1;
    std::cout << "allocated an int at " << (void*)p2 << '\n';
 
    // 分配一个 int ,对齐于 32 字节边界
    int* p3 = a.aligned_alloc<int>(32);
    if (p3)
        *p3 = 2;
    std::cout << "allocated an int at " << (void*)p3 << " (32 byte alignment)\n";
}

Insert picture description here

std::aligned_storage

head File

  • <type_traits>

prototype

template< std::size_t Len, std::size_t Align = /*default-alignment*/ >
struct aligned_storage;

Official document: https://zh.cppreference.com/w/cpp/types/aligned_storage

Guess you like

Origin blog.csdn.net/zhizhengguan/article/details/114682837