20

We all know that sizeof an empty class or an object of empty class will be 1 byte. I came across something where sizeof a class and its object is coming as 0. The program is syntactically correct as there were no compilation or run time errors. Is this undefined behavior? The use case I'm trying to execute makes any sense and looks like a valid one? Is it a big blunder to not to give exact subscript or size for an array in the class? The code snippet is as below:

#include<iostream>
using namespace std;
class A
{
   char a[];
};
int main()
{
    A b;
    cout<<sizeof(A)<<endl;
    cout<<sizeof(b)<<endl;
    return 0;
}

output:

0

0

The sizeof an empty class is one byte (non zero basically) and the reason for that is said like "To make sure that different objects have different addresses".

What happens in this case then when sizeof class is coming a zero? Note: Observed the same behavior for int a[] as well.

Community
  • 1
  • 1
Divya
  • 393
  • 2
  • 5
  • 17

5 Answers5

25

It's called "flexible array member" and it's a feature of C99 (I think). It's not valid C++ - you don't have warnings/errors, probably because the compiler supports it as an extension.

Compiling with -Wall -Wextra -pedantic -std=c++NN (98, 03, 11, 14, ..) should generate warning (the last two flags will disable any compiler extensions).


You can see some information in this related question: Is using flexible array members in C bad practice?

For example, here's what GCC says about this:

In ISO C99, you would use a flexible array member, which is slightly different in syntax and semantics:
...
Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero.

(source: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html).

This explains the 0 size of char a[] and not the 0 for the class, but as I already mentioned - it's a C feature and not a valid C++.

Community
  • 1
  • 1
Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
6

If you compile with -pedantic flag

$ g++ -W -Wall -pedantic prog.cpp
prog.cpp:5:11: warning: ISO C++ forbids zero-size array ‘a’ [-pedantic]

C++ does not support VLAs and thus your class declaration is not legal and going outside the scope of standard C++ rules.

Ry-
  • 218,210
  • 55
  • 464
  • 476
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
1

Your code is not standard C++, thus I can not see any sense in that.

If you use pedantic flag, you should receive this:

gsamaras@pythagoras:~$ g++ -pedantic file.cpp
file.cpp:5:11: warning: ISO C++ forbids zero-size array ‘a’ [-Wpedantic]
    char a[];
         ^

Try changing your class to

class A {
   char a[5];
};

then you should get an output of

5
5

like you should expect.

However, you can argue that without the flag, your code does compile and outputs zeroes. As a counter I could say that the same goes if you use this class:

class A {
   char a[0];
};

but I am pretty sure you know that zero-sized arrays are not allowed, but still this thing compiles fine and gives an output of zeroes.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
-1

Empty base classes can be optimized to zero bytes, which would technically make sizeof(base) also 0.

The "1 byte" thing is really an implementation detail, coming from the rule that distinct objects need to have distinct addresses.

So:

struct base { };

struct derived : base { };

Both sizeof(base) and sizeof(derived) are allowed to be 0, because the derived object is the same object as the base object contained within.

However:

struct base1 { };
struct base2 { };
struct derived : base1, base2 { };

Here, sizeof(derived) must be 1, because the standard requires that

derived d;
assert(static_cast<base1 *>(&d) != static_cast<base2 *>(&d));

Similarly:

struct type1 { };
struct type2 { };
struct combined { type1 obj1; type2 obj2; };

requires that

combined c;
assert(&c.obj1 != &c.obj2);

Many compiler vendors take the shortcut and simply make empty classes take up one byte.

Simon Richter
  • 28,572
  • 1
  • 42
  • 64
  • I'm not sure I follow. I understand that an empty `derived` need not contain anything that the `base` does not, and thus would not need to be any larger than `base`, but I would think an array of ten `base` instances would need to occupy ten distinct, as would an array of ten `derived` instances. Even if `derived` contained a `char` as well as inheriting `base`, I think the addresses of the `base` instances would need to be distinct from those of the `char` fields, though I'm not positive on that. – supercat May 13 '15 at 15:50
  • No, that is an optimization that is specifically allowed ([EBCO](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Empty_Base_Optimization)). – Simon Richter May 13 '15 at 15:57
  • EBCO may be allowed, but to my mind all that would mean is that a class with an empty base is allowed to treat the inherited base as though it were a `union` field rather than a `struct` field (though I'm curious what that would happen if an empty base were passed by reference to a method which overwrote it with another object of base type; I think slicing is defined behavior in many cases, and it would behave sanely here if the base class had its own allocated space, but I don't see how it could work here). – supercat May 13 '15 at 16:22
  • 2
    *"Both `sizeof(base)` and `sizeof(derived)` are allowed to be 0"* No, they're not. However, `sizeof(derived) == sizeof(base)` is allowed, as well as for `struct derived : base { int m; };`, `sizeof(derived) == sizeof(int)` is allowed. While any *complete* object *must* occupy address space (because of the unique address), base class subobjects are not complete objects and hence `derived d; (void*)&d == (void*)(base*)&b` is allowed. [intro.object]p5 *"Unless it is a bit-field, a most derived object shall have a non-zero size and shall occupy one or more bytes of storage."* – dyp May 13 '15 at 16:24
-1

The size of a class can be 0. Consider the following piece of code

#include <iostream>
using namespace std;

class A
{
    public:
    int a[0];
    void getA(){
        cout<<"Hello World";
    }
};

class B
{
};

int main()
{
    cout<<"The size of A is "<<sizeof(A)<<endl;   // prints 0
    A w;
    cout<<"The size of object of A is "<<sizeof(w)<<endl;    //prints 0
    cout<<"The size of the array a in A is "<<sizeof(w.a)<<endl;  // prints 0
    cout<<"The value from function of class A is  "<<w.getA()<<endl;  // Gives a compilation error
    cout<<"The size of B is "<<sizeof(B)<<endl;  //prints 1
}


Output:
The size of A is 0
The size of object of A is 0
The size of the array a in A is 0
The size of B is 1

So, accessing functions present in the class with a size 0 leads to compilation error.

Rahul
  • 1
  • 1