Do Unused Functions Get Optimized Out

Do unused functions get optimized out?

It depends on the compiler. Visual C++ 9 can do that - unused static functions are removed at compilation phase (there's even a C4505 warning for that), unused functions with external linkage can be removed at link phase depending on linker settings.

Can unused function-arguments become optimized away?

You don't need to worry - the lifetime of the function argument at the call site is guaranteed to survive the function call. (This is why things like foo(s.c_str()) for a std::string s work.)

A compiler is not allowed to break that rule, subject to as if rule flexibility.

Does an unused member variable take up memory?

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.

Do compilers remove unused functions, definitions, variables, macros, includes, etc.?

It depends:

Macros are converted to program text by the compiler. They
don't represent anything other than the text that replaces them,
and don't live beyond compile time (unless... see below).

Local variables and such are likely removed, if they don't have
a non-trivial constructor or destructor. (You don't want
something like scoped_lock removed just because you don't
reference it later in the code.) The same holds for variables
and functions with internal linkage (e.g. defined at namespace
scope with the keyword static). In most cases, however, the
resources needed for such objects is minimal, and the savings
negligible.

Functions are a bit different, and depend. A virtual function
will generally be considered "used" if there is ever an instance
of its type, and it will almost certainly be present, even if it
is never called.

Beyond that (and this applies to global variables as well): it's
up to the linker. The granularity of most linkers is the object
file which results from compiling a "translation unit": that
object file either is or is not part of your program. If you
tell the linker to incorporate the object file, then you should
get everything that is in it. If you put the object file in
a (static) library, and tell the linker to use this, then the
linker will incorporate the object file into your program if and
only if it resolves an otherwise unresolved external. But if it
incorporates the object file, it generally will incorporate all
of it. (Any good library will put each non-virtual function in
a separate object file, so you don't get more than you need.)

In this regard, DLL's behave like object files (despite their
name). If you link your object files into a DLL, the program
which uses it will get all of the DLL, or none.

Finally: although not part of your program, the object files and
the final executable will often contain symbolic information;
the best systems will even maintain information concerning
macros, so that the debugger can display things the way you
wrote them. (How far it can do this with macros is debatable.)

Will compiler optimize out unused arguments of static function?

I wrote this nonsense program to test this. It's got some nonsense code in the function, and calls it several times because otherwise the compiler just inlined the whole function call making the test useless. (It's a strange mix of C and C++ i know.... stupid code, but still works to demonstrate the issue)

#include <stdio.h>
#include <vector>
#include <algorithm>

using namespace std;

struct demo
{
int a;
char ch;
};

static void __fastcall func(struct demo* d)
{
for(int i = 0; i < 100; i++) {
std::vector<int> a;
std::sort(begin(a), end(a));
printf("THis is a test");
printf("Hello world\n");
}
}

int main()
{
// void*p = (void*)&func;
struct demo d;
func(&d);
func(&d);
func(&d);
func(&d);
func(&d);
func(&d);
func(&d);
//printf((const char*)p);
}

As written there the function calls compile to this :-

    func(&d);
00F61096 call func (0F61000h)
func(&d);
00F6109B call func (0F61000h)
func(&d);
00F610A0 call func (0F61000h)
func(&d);
00F610A5 call func (0F61000h)
func(&d);
00F610AA call func (0F61000h)
func(&d);
00F610AF call func (0F61000h)
func(&d);
00F610B4 call func (0F61000h)

Which shows that the compiler will indeed omit the parameter if it's not used.
However if I uncomment the two lines there to take the address of the function then things change, it instead generated this :-

00C71099  lea         ecx,[esp]  
00C7109C call func (0C71000h)
func(&d);
00C710A1 lea ecx,[esp]
00C710A4 call func (0C71000h)
func(&d);
00C710A9 lea ecx,[esp]
00C710AC call func (0C71000h)
func(&d);
00C710B1 lea ecx,[esp]
00C710B4 call func (0C71000h)

Where it DOES send the pointer.

My assumption is that in the first case the compiler is able to prove that if it generated a custom calling convention to the function that there cannot possibly be any user visible effects but in the second case where you take a pointer to the function, the function could be called from another separately compiled module which has no way to know if the parameter is needed or not. Although in this case it wouldn't matter if the register was set or not, in general the compiler needs to stickl to the exact calling pattern so I assume that it generated the most general code and won't use custom calling conventions if it can't prove that it can see all the code that could call the function.

Anyway, to answer the question, no the compiler will not always pass unused parameters in a register but I certainly wouldn't write any code that depends on any specific behavior here as changing unrelated code elsewhere (taking the address of the function), changed that behavior and there is nothing guaranteed by any standard that I can see.

gcc -O0 still optimizes out unused code. Is there a compile flag to change that?

GCC doesn't "optimize out" anything here. It just doesn't generate useless code. It seems to a very common illusion that there's some pure form of code that the compiler should generate and any changes to that are an "optimization". There is no such thing.

The compiler creates some data structure that represents what the code means, then it applies some transformations on that data structure and from that it generates assembler that then gets compiled down to instructions. If you compile without "optimizations" it just means that the compiler will only do the least effort possible to generate code.

In this case, the whole statement is useless because it doesn't do anything and is thrown away immediately (after expanding the inlines and what the builtins mean it is equivalent to writing a/b;, the difference is that writing a/b; will emit a warning about statement with no effect while the builtins probably aren't handled by the same warnings). This is not an optimization, the compiler would actually have to expend extra effort to invent meaning to a meaningless statement, then fake a temporary variable to store the result of this statement to then throw it away.

What you're looking for is not flags to disable optimizations, but pessimization flags. I don't think any compiler developers waste time implementing such flags. Other than maybe as an April fools joke.

Will Rust optimize away unused function arguments?

Will Rust optimize away unused function arguments?

Yes, LLVM (the backend for rustc) is able to optimize away unused variables when removing them does not change program behavior, although nothing guarantees it will do it. rustc has some passes before LLVM too, but the same applies.

Knowing what exactly counts as program behavior is tricky business. However, multi-threaded primitives used in refcounting mechanics are usually the sort of thing that cannot be optimized away for good reason. Refer to the Rust reference for more information (other resources that might help are the nomicon, the different GitHub repos, the Rust fora, the C++11 memory model which Rust uses, etc.).

On the other hand, if you are asking about what are the semantics of the language when it encounters unused parameters, then no, Rust does not ignore them (and hopefully never will!).

will the y be optimized away?

No, it is a type with side effects. For instance, dropping it requires running non-trivial code.

The intention of the y is to limit the number of the running instances of f

Such an arrangement does not limit how many threads are running f since Arc is not a mutex and, even if it were some kind of mutex, you could construct as many independent ones as you wanted.

Can unused data members be optimized out in C++

I don't know about the compiler optimizations, but you can get rid of the warnings in two ways: Either use pragmas:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
class A{
//...
};
#pragma clang diagnostic pop

or, which is probably better suited for you, include a fake friend function in your class:

class A{
friend void i_do_not_exist();
//...
};

In that way, the compiler cannot know if the field is used or not. Therefore, it does not complain and will definitely not throw anything out. This can lead to safety issues if the i_do_not_exist() function is ever defined anywhere, as that function is given direct access to the private members of the class.

A third solution is to define a dummy function which access the padding_ member:

class A {
private:
void ignore_padding__() { padding_[0] = 0; }
//...
};


Related Topics



Leave a reply



Submit