0

I have the following class and its alignment is 8 and the size is 40 bytes (compiled using gcc v11.2 64-bit).

class Foo
{
public:
    inline Foo( ) = default;
    /*
       other member functions such as
       move ctor and move assignment operator etc.
    */
    

private:
    mutable std::vector< std::vector<char> > _characterMatrix; // occupies 24 bytes on the stack
    int _Y_AxisLen; // 4 bytes
    int _X_AxisLen; // 4 bytes
    char _fillCharacter; // 1 byte

    inline static const std::unordered_set<char> CHAR_SET { '/', '\\', '|', '-' }; // static member so nothing
};

And this statement:

std::cout << "Size: " << sizeof( Foo) << " --- alignment: " << alignof( Foo) << '\n';

generates this: Size: 40 --- alignment: 8

The size of the class is actually 8(size member of vector obj) + 8(capacity member of vector obj) + 8(qword pointer to the memory block on the heap for vector obj) + 4(int) + 4(int) + 1(char). So 8+8+8+4+4+1 == 33 bytes. And 7 bytes are being wasted every time an instance of Foo is initialized.

So my question is that is there a way to decrease the size down to 36?

I also tried this:

class alignas( 4 ) Foo
{
public:
    inline Foo( ) = default;
    /*
       other member functions such as
       move ctor and move assignment operator etc.
    */
    

private:
    mutable std::vector< std::vector<char> > _characterMatrix;
    int _Y_AxisLen;
    int _X_AxisLen;
    char _fillCharacter;

    inline static const std::unordered_set<char> CHAR_SET { '/', '\\', '|', '-' };
};

But the compiler ignored the alignas(4). The size was still 40.

Edit: After reading some of the comments I noticed that in many STL implementations, a std::vector object does not have a pointer and a size and a capacity member but instead it has 3 pointers(each one 8 bytes in size so again 24 in total).

digito_evo
  • 3,216
  • 2
  • 14
  • 42
  • See [Why isn't sizeof for a struct equal to the sum of sizeof of each member?](https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member) – Some programmer dude Nov 04 '21 at 08:01
  • 4
    On a totally unrelated note: Also see [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) Symbols beginning with an underscore followed by an upper-case letter (like e.g. `_Y_AxisLen`) are reserved in all scopes and situations. – Some programmer dude Nov 04 '21 at 08:02
  • AFAIK - vector is 3 pointers (begin, end, end_of_capacity) - but still -- it is the same size as you wrote (3*p == p + s + c) – PiotrNycz Nov 04 '21 at 08:07
  • If pointer it 8B on you system - you cannot do anything to improve your struct size. To visualize the problem -- imagine you have `Foo foo[2];` array. `Foo[1]` has to start at memory address suitable for pointers (modulo 8) -- so this extra 7 bytes of padding – PiotrNycz Nov 04 '21 at 08:10
  • It is simple to have a size of 36: just compile as 32 bits. In a 64 bits architecture, compilers try to align pointers on 8 bytes boundaries, and as you pointed out, a vector contains pointers. – Serge Ballesta Nov 04 '21 at 08:11
  • 1
    @PiotrNycz the standard library could implement it as 3 pointers or 1 pointer and 2 sizes (or something else entirely), the standard doesn't dictate the implementation only the required behaviour – Alan Birtles Nov 04 '21 at 08:20
  • Changing the alignment is not to be taken lightly, wrong alignment can cause SIGBUS or make your program much slower then it should be. Depending on your platform/hardware, 7 bytes wasted can be close to nothing. Don't do premature optimizations unless you absolutely need to. – Kaldrr Nov 04 '21 at 08:27
  • Could you use `short int` for the axis lengths? – Den-Jason Nov 04 '21 at 08:41
  • 3
    You are worried about saving 4 bytes *and* you represent a matrix with a vector of vectors. These two don't belong in the same room. – n. m. could be an AI Nov 04 '21 at 09:11
  • @AlanBirtles but *usually* `sizeof(std::vector::size_type) == sizeof(T*)`, so it's the same size in most cases – Caleth Nov 04 '21 at 09:20
  • I was going to suggest `[[gnu::packed]]`, but that just leads to "warning: ignoring packed attribute because of unpacked non-POD field 'std::vector > Foo::_characterMatrix'" – Caleth Nov 04 '21 at 09:26
  • Your second program is [ill-formed](https://en.cppreference.com/w/cpp/language/alignas): "If the strictest (largest) alignas on a declaration is weaker than the alignment it would have without any alignas specifiers (that is, weaker than its natural alignment or weaker than alignas on another declaration of the same object or type), the program is ill-formed" – Caleth Nov 04 '21 at 09:28
  • @Den-Jason I think it is possible. But a few months ago I decided to use int instead of short int due to some reasons that I don't remember now (unfortunately). – digito_evo Nov 04 '21 at 11:18
  • @Some programmer dude Thanks for pointing this out. I will apply the required changes to my code. – digito_evo Nov 04 '21 at 11:32

2 Answers2

2

You can use bit-fields to control exactly how big your members are.

class Foo {
public:
    Foo() = default;
    ~Foo() = default;

    Foo(const Foo & other)
    : matrix(std::make_unique_for_overwrite<char[]>(other.size())), 
      Y_AxisLen(other.Y_AxisLen),
      X_AxisLen(other.X_AxisLen),
      FillCharacter(other.FillCharacter) {
        std::copy(other.matrix.get(), other.matrix.get() + size(), matrix.get());
    }
    Foo(Foo && other) = default;

    Foo& operator=(Foo other) {
        swap(matrix, other.matrix);
        Y_AxisLen = other.Y_AxisLen;
        X_AxisLen = other.X_AxisLen;
        FillCharacter = other.FillCharacter;
        return *this;
    }

private:
    static_assert(CHAR_BIT == 8, "Bit fields assume 8 bit char");

    std::size_t size() const { return Y_AxisLen * X_AxisLen; }

    // support [x][y] access
    char* operator[](size_t col) const { return matrix.get() + (col * Y_AxisLen); }

    std::unique_ptr<char[]> matrix;
    std::uint64_t Y_AxisLen : 28;
    std::uint64_t X_AxisLen : 28;
    std::uint64_t FillCharacter : 8;

    inline static const std::unordered_set<char> CHAR_SET { '/', '\\', '|', '-' };
};

As a bonus, this has a size of 16 (but still an alignment of 8)

Caleth
  • 52,200
  • 2
  • 44
  • 75
0
class Foo
    {
    public:
        inline Foo( ) = default;
        
    
    private:
        char **  _characterMatrix;  // 8 bytes
        int _Y_AxisLen; // 4 bytes
        int _X_AxisLen; // 4 bytes
        char _fillCharacter; // 1 byte
        intline static const std::unordered_set<char> CHAR_SET { '/', '\\', '|', '-' };  
    };
Anis Belaid
  • 304
  • 2
  • 8