4

In C++, I can determine the size of a class object using sizeof(my_class).

However, there seems to be no equivalent operator for the static part of a class.

Is there something like sizeof(static my_class) in C++?

iFreilicht
  • 13,271
  • 9
  • 43
  • 74
  • 8
    Why do you need to know this? – Barmar Aug 15 '17 at 17:02
  • 1
    There's no reflection in C++ so you may have to call this yourself, `sizeof(static_a) + sizeof(static_b) + ...`. If any of these are pointers to something allocated your estimates will be *way* off though. – tadman Aug 15 '17 at 17:03
  • `sizeof(my_class)` will also include the size of the vtable pointer. – Barmar Aug 15 '17 at 17:05
  • Don't forget about padding. `sizeof(my_class)` might not be the size of every variable in the class. – Rakete1111 Aug 15 '17 at 17:05
  • Good points, I removed the statement about the size of the class object being equal to the sum of the non-static member variables. – iFreilicht Aug 15 '17 at 17:06
  • @Barmar mainly for diagnostic reasons as I'm working with very tight memory constraints. I can get that information in other ways, though. – iFreilicht Aug 15 '17 at 17:09
  • I’m not sure, but I think a class’s static variables might be guaranteed contiguous by some compilers (not by the language itself, though, I’m almost certain). In that case you can do pointer arithmetic (again, the language says that that’s UB but the compiler might be more generous) to get the size. – Daniel H Aug 15 '17 at 17:12
  • 1
    @DanielH Pointer arithmetic on what? The static variables aren't located anywhere near the objects. – Barmar Aug 15 '17 at 17:15
  • @tadman That's probably the best estimate, although it won't include padding between the variables, just like adding the sizes of regular members isn't the same as the size of the class. – Barmar Aug 15 '17 at 17:16
  • @Barmar The first and last static variable declared. It is definitely not a preferred method if the information’s available some other way, but I expect `(sizeof(C::last_static) + (static_cast(&C::last_static) - static_cast(&C::first_static))` would work on at least several platforms. – Daniel H Aug 15 '17 at 17:18
  • @DanielH Yes, that looks like it could be right. Post it as an answer, with an appropriate caveat that it's implementation-dependent. – Barmar Aug 15 '17 at 17:20
  • @Barmar Yeah, it's going to be a spitball estimate at best. – tadman Aug 15 '17 at 17:41
  • @Barmar Since iFreilicht can get the information in other ways, I am fairly sure those other ways are better. Especially because I don’t know for sure about any compilers making the guarantees I mentioned; I just think I heard about them somewhere. I think it’s at most a suggestion for where to start looking, than an actual answer. – Daniel H Aug 15 '17 at 17:45
  • @DanielH - this most certainly wont' work on most modern compilers unless all variables end up in the same _section_. Since the section used depends on whether the data is `const`, is zero-initialized, has a compile-time vs runtime initializer, the "size" of the data, and more it is very likely that static variables will end up in a variety of sections. – BeeOnRope Oct 10 '17 at 02:09

1 Answers1

2

You won't find a legal or portable1 way to do this in standard C++, but you can certainly use platform-specific tools to examine the binary to get an estimate of the size of global data.

On Unix platforms you can use one of a variety of ELF-format reading tools to dump the symbol table along with sizes. For example, something like:

nm --demangle --print-size a.out | egrep -i ' [bdgsr] '

Will dump the size (as the second field in the output) of all the global data in the .bss, .data, .rodata and related sections2.

The --demangle argument gives you human-readable names from the C++ mangled names. The egrep at the end of the pipeline restricts the symbols to the ones that are typically used for static variables (i.e., it omits the symbols for functions). Given the following class:

class Foo {

        static void StaticFunction();

        void MemberFunction();

        static int some_int_s;
        static long some_zero_long;
        static char some_char_array[];
        static const char *some_const_string;
};

int Foo::some_int_s = 5;
char Foo::some_char_array[42];
const char* Foo::some_const_string = "hello, world?";

void Foo::StaticFunction() {
}

void Foo::MemberFunction() {
        static double f = 0.5;
}

... and compiled with g++, the nm command given above outputs:

0000000000000000 0000000000000004 D Foo::some_int_s
0000000000000000 000000000000002a B Foo::some_char_array
0000000000000008 0000000000000008 D Foo::some_const_string
0000000000000010 0000000000000008 d Foo::MemberFunction()::f

The second column is the size of the global: 4 bytes for the int, 0x2a (42) bytes for the char[] and so on. Note that it includes function-local static variables as well, which is probably what you want since they take up size like anything else. You can use another grep to restrict to specific classes.

Note that Foo::some_const_string has size 8, despite having the value hello, world? which is 14 characters (including the terminating null). In fact, you'll find that any const char * or any static pointer at all will have size 8 on a 64-bit platform, since that is the size of the pointer itself. The actual data for the string literal (the characters h,e,l,l,o,...) are stored somewhere else and this size is not reported by nm. In general determining the size of string literals is complex and may not have a straightforward answer (i.e,. several classes may share the same underlying literal data). You'll probably have to script something like readelf if you really want to include that in your accounting.

You might also try existing binary sizing utilities, such as Bloaty McBloatface, although a quick scan of the readme seems to indicate that it might simply give the same information as above (for example, it doesn't seem to handle the "string literal" issue).


1 For example, a comment mentions an approach of taking the difference of the "first" and "last" statics at runtime to estimate the size, but in addition to being undefined behavior it isn't likely to work in practice since global variables get spread across various sections in the binary, such as .bss, .data, .rodata and other variants of those, so a simply subtraction will almost certainly return the wrong result for many classes.

2 In particular, when I say "related" I mean that it dumps the size of the "small" initialized and uninitialized sections in addition to the default ones.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386