The golden C++ "as-if" rule1 states that, if the observable behavior of a program doesn't depend on an unused data-member existence, the compiler is allowed to optimized it away.
Does an unused member variable take up memory?
No (if it is "really" unused).
Now comes two questions in mind:
- When would the observable behavior not depend on a member existence?
- Does that kind of situations occurs in real life programs?
Let's start with an example.
Example
#include <iostream>
struct Foo1
{ int var1 = 5; Foo1() { std::cout << var1; } };
struct Foo2
{ int var1 = 5; int var2; Foo2() { std::cout << var1; } };
void f1() { (void) Foo1{}; }
void f2() { (void) Foo2{}; }
If we ask gcc to compile this translation unit, it outputs:
f1():
mov esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
f2():
jmp f1()
f2
is the same as f1
, and no memory is ever used to hold an actual Foo2::var2
. (Clang does something similar).
Discussion
Some may say this is different for two reasons:
- this is too trivial an example,
- the struct is entirely optimized, it doesn't count.
Well, a good program is a smart and complex assembly of simple things rather than a simple juxtaposition of complex things. In real life, you write tons of simple functions using simple structures than the compiler optimizes away. For instance:
bool insert(std::set<int>& set, int value)
{
return set.insert(value).second;
}
This is a genuine example of a data-member (here, std::pair<std::set<int>::iterator, bool>::first
) being unused. Guess what? It is optimized away (simpler example with a dummy set if that assembly makes you cry).
Now would be the perfect time to read the excellent answer of Max Langhof (upvote it for me please). It explains why, in the end, the concept of structure doesn't make sense at the assembly level the compiler outputs.
"But, if I do X, the fact that the unused member is optimized away is a problem!"
There have been a number of comments arguing this answer must be wrong because some operation (like assert(sizeof(Foo2) == 2*sizeof(int))
) would break something.
If X is part of the observable behavior of the program2, the compiler is not allowed to optimized things away. There are a lot of operations on an object containing an "unused" data-member which would have an observable effect on the program. If such an operation is performed or if the compiler cannot prove none is performed, that "unused" data-member is part of the observable behavior of the program and cannot be optimized away.
Operations that affect the observable behavior include, but are not limited to:
- taking the size of a type of object (
sizeof(Foo)
),
- taking the address of a data member declared after the "unused" one,
- copying the object with a function like
memcpy
,
- manipulating the representation of the object (like with
memcmp
),
- qualifying an object as volatile,
- etc.
1)
[intro.abstract]/1
The semantic descriptions in this document define a parameterized nondeterministic abstract machine. This document places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.
2) Like an assert passing or failing is.
var2
doesn't. – Producesizeof(Foo)
cannot decrease by definition - if you printsizeof(Foo)
it must yield8
(on common platforms). Compilers can optimize away the space used byvar2
(no matter if throughnew
or on the stack or in function calls...) in any context they find it reasonable, even without LTO or whole program optimization. Where that is not possible they won't do it, as with just about any other optimization. I believe the edit to the accepted answer makes it significantly less likely to be misled by it. – PrimulaceousFoo
doesn't have standard layout)? And sure, compilers can do anything which adhere the as-if rule. The question is, do they optimize awayvar2
in non-trivial cases? And the answer is: no, they don't,var2
will still take up memory. Theory is one thing (what can a compiler do), and practice is another (what capabilities compilers have). In my opinion the accepted answer still misleading. Anyways, I may misinterpret the intent of the question, considering the lot of upvotes of the accepted answer. – PredatoryFoo
usingsizeof
it will, by definition, not ignore unused variables. But even that is a moot point because the only thing the standard guarantees are the side effects of your program, not how that is accomplished.memcpy
andsizeof
themselves have no side effects, which is why this optimization can happen. – Primulaceousvar2
(this can be done, it just needs whole program optimization - but no current compiler has such a feature), then sizeof will be 4. And it is not forbidden by the standard, as far as I know (I could be wrong in this matter). Is there anything in the standard, which guarantee some property of sizeof of structs? You say "by definition". Can you cite this definition which says it cannot ignore unused variables? – Predatorystd::cout << sizeof(Foo) << std::endl;
depend on whole program optimization. As in, the above expression results in a specific side-effect in the abstract machine model, and optimization is not allowed to change this side effect. – Primulaceous8
(or whatever) even after internally changingsizeof(Foo)
to4
, but the discussion becomes pointless here because nothing in the standard actually exists outside the abstract machine model. An implementation could use flying unicorns instead of bytes and still be standard-conforming, as long as the observable behavior produced is as specified. – Primulaceousvar2
optimized away, it can be 4 (whole program optimization is needed for the compiler to prove thatvar2
is really not used). It just says that it returns the number of bytes occupied. Ifvar2
removed, it would occupy 4. Your argument about "optimization changes side effect" is a valid one, though. Anyways, my important argument is not this, and even, ifsizeof(Foo)
is not allowed to change, it just supports my important comment: the accepted answer is misleading. – Predatoryvar2
will take up space. And this fact is not mentioned in the answer. – Predatorysizeof(Foo)
can be 4. If it returned 8, while the real size is 4, it would cause a lot of problems. Consider:var2
is not used at all in a program, so compiler removes it. Now,sizeof(Foo)
is 4. The number of bytes it occupies. Does this against the standard? No. (at least I don't know any rule which is violated). If this optimization is done reliably (always, if it is possible), then it is not the "optimization changes side" effect any more, becausesizeof(Foo)
was never 8. – Predatoryvar2
,sizeof(Foo)
will become 8. But then, this is a different program, so it is logical that it has a different side effect. – Predatory