Reputation: 429
I have enabled compiler and linker optimization in the hopes of removing all unused code/data from my ARM32 executable. From my map file, I can see that the unused sections of code were indeed discarded so the optimization flags are mostly working, except for static member functions/variables of an unused class. Any ideas on how to get rid of this would be really appreciated, because this adds up to quite a bit on a resource-constrained embedded platform!
Here's an MVCE compiled using g++ 7.5. on Ubuntu 18.04
#include <string>
#include <iostream>
#include <string.h>
class unusedClass {
public:
unusedClass() {};
~unusedClass() {};
static std::string className;
void initArray(void) {
memset(a, 0, sizeof(a));
}
void printArray(void) {
for (auto& i:a) {
std::cout<< i << std::endl;
}
}
static void printClassName(void) {
std::cout << "This is a static member function of the class \"UNUSED CLASS\""<< std::endl;
}
private:
int a[1000];
};
std::string unusedClass::className = "unusedClass";
int main() {
std::cout << "Running the Clean-up Dead Code Test" << std::endl;
return 0;
}
Compile with optimization flags to remove unused code
g++ -Os -flto test.cpp
To check if the static member variable was compiled into the executable
readelf -a --wide a.out | awk '$4 == "OBJECT" { print }'
29: 0000000000201140 32 OBJECT LOCAL DEFAULT 24 _ZN11unusedClass9classNameB5cxx11E
Upvotes: 0
Views: 2416
Reputation: 25643
That is really strange! I simplified your code example and added some variables and functions to see if the observation is valid in general.
uc.h:
#include <string>
extern std::string mymystring1;
extern std::string mymystring2;
extern int mymyx1;
extern int mymyx2;
int mymyf1();
int mymyf
uc.cpp:
#include <string>
std::string mymystring1 ="This is a very simple test what happens if fdata sections did not work!";
std::string mymystring2 ="This variable should be used";
int mymyx1 = 11111; // unused
int mymyx2 = 22222;
int mymyf1() { return 1; } // unused
int mymyf2() { return 2; }
main.cpp:
#include "uc.h"
int main() {
std::cout << mymystring1 << std::endl;
std::cout << mymyx1 << std::endl;
std::cout << mymyf1() << std::endl;
return 0;
}
Even if I compile and link with uc.cpp as a library, I get the unused string object linked in my code.
63: 00000000004041c0 32 OBJECT GLOBAL DEFAULT 24 _Z11mymystring1B5cxx11
78: 00000000004041a0 32 OBJECT GLOBAL DEFAULT 24 _Z11mymystring2B5cxx11
90: 0000000000404070 4 OBJECT GLOBAL DEFAULT 23 mymyx1
Looking inside the uc.o file with objdump -h uc.o
11 .data.mymyx2 00000004 0000000000000000 0000000000000000 000001d0 2**2
CONTENTS, ALLOC, LOAD, DATA
12 .data.mymyx1 00000004 0000000000000000 0000000000000000 000001d4 2**2
CONTENTS, ALLOC, LOAD, DATA
13 .bss._Z11mymystring2B5cxx11 00000020 0000000000000000 0000000000000000 000001e0 2**5
ALLOC
14 .bss._Z11mymystring1B5cxx11 00000020 0000000000000000 0000000000000000 000001e0 2**5
ALLOC
We see that the data for mymyx1 and mymyx2 are in separate data sections. String data generates bss sections? And where is the data?
OK, we take a look: objdump -s go
Contents of section .rodata:
402000 62617369 635f7374 72696e67 3a3a5f4d basic_string::_M
402010 5f636f6e 73747275 6374206e 756c6c20 _construct null
402020 6e6f7420 76616c69 64005468 69732069 not valid.This i
402030 73206120 76657279 2073696d 706c6520 s a very simple
402040 74657374 20776861 74206861 7070656e test what happen
402050 73206966 20666461 74612073 65637469 s if fdata secti
402060 6f6e7320 64696420 6e6f7420 776f726b ons did not work
402070 21005468 69732076 61726961 626c6520 !.This variable
402080 73686f75 6c642062 65207573 656400 should be used.
If I move the second string to another file, it is removed!
Contents of section .rodata:
402000 54686973 20697320 61207665 72792073 This is a very s
402010 696d706c 65207465 73742077 68617420 imple test what
402020 68617070 656e7320 69662066 64617461 happens if fdata
402030 20736563 74696f6e 73206469 64206e6f sections did no
402040 7420776f 726b2100 t work!.
For me this is simply a compiler/linker bug!
From J.Schulke answer:
static member variables can't just be optimized away because they could be accessed in multiple translation units. They must be compiled so that the linker knows when the same static variable is used in different places.
They must be compiled, but they can be removed in link stage, if they are never accessed. As given in the above code, functions and the integer variables are removed if not used but std::string
vars are not if they are in the same file with used data. If we move each std::string variable in a separate file and linking via a static library, they are moved out. This should also be happen if we have -fdata-sections
in place, but is not working here! Why? I don't know.
Upvotes: 2
Reputation: 39859
As StoryTeller pointer out, _ZN11unusedClass9classNameB5cxx11E
is not a member function, but the member variable static std::string className
.
static
member variables can't just be optimized away because they could be accessed in multiple translation units. They must be compiled so that the linker knows when the same static variable is used in different places.
constexpr
If you want functions and variables to not be compiled into the executable, using constexpr instead of static
often leads to this result.
In your example, the class name is known at compile time, so using constexpr
would be the more idiomatic solution anyways.
constexpr
implies inline
for functions and implies "inlinability" for variables. Since the values of constexpr
variables are all known at compile time, there is no need for them to be stored in the compiled binary. This is why the compiler will optimize them out if unused, even on -O0
.
Here's a working example, in which the variable is not compiled: https://godbolt.org/z/bqsuTA
Note: You can only use const char *
in a constexpr context, not std::string
.
Edit: If you used a static const char*
instead, it would also not be optimized out. Only constexpr
leads to the desired result: https://godbolt.org/z/DYNk2G
namespace
See the following example:
namespace {
struct unused1 {;
static const int x;
};
}
struct unused2 {;
static const int x;
};
const int unused1::x = 1;
const int unused2::x = 2;
Out of the two variables .long 2
will always be found in the binary, even on -O3
.
unused2::x:
.long 2
Putting a class into an anonymous namespace is akin to using the static
modifier for a function and makes compilation of these unused constants unnecessary.
Warning: If you put this class into an anonymous namespace, different translation units will no longer use the same static
variable, but their own copy of it!
Upvotes: 4