Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does an unused member variable take up memory?

Tags:

c++

memory

struct

Does initializing a member variable and not referencing/using it further take up RAM during runtime, or does the compiler simply ignore that variable?

struct Foo {     int var1;     int var2;      Foo() { var1 = 5; std::cout << var1; } }; 

In the example above, the member 'var1' gets a value which is then displayed in the console. 'Var2', however, is not used at all. Therefore writing it to memory during runtime would be a waste of resources. Does the compiler take these kinds of situations into an account and simply ignore unused variables, or is the Foo object always the same size, regardless of whether its members are used?

like image 810
Chriss555888 Avatar asked Mar 08 '19 10:03

Chriss555888


People also ask

Is a variable stored in RAM?

Variables are usually stored in RAM. This is either on the heap (e.g. all global variables will usually go there) or on the stack (all variables declared within a method/function usually go there). Stack and Heap are both RAM, just different locations.

How are variables stored in memory CPP?

Variables are usually stored in RAM. This is either on the Heap (e.g. global variables, static variables in methods/functions) or on the Stack (e.g. non-static variables declared within a method/function). Stack and Heap are both RAM, just different locations.

How do I remove unused variables in R?

Remove unused/unnecessary objects using rm("Object_name") Use gc() Check memory cleared using memory. size()


1 Answers

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:

  1. When would the observable behavior not depend on a member existence?
  2. 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:

  1. this is too trivial an example,
  2. 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.

like image 129
YSC Avatar answered Oct 02 '22 14:10

YSC