The setup (source files are listed at the bottom). There is a Counters
class with 2 counter members and 2 methods to increment them and a 3rd extra #ifndef NDEBUG
-guarded "debug" counter and its incrementer. These incrementer methods are provided through additional functions defined in their respective headers.
The bug. This is a contrived scenario, but the "-DNDEBUG
" compilation flag is provided for only one of the two headers. The effect of this is that the wrong counter is incremented in the final program: main.cpp
prints:
$ CFLAGS='-DNDEBUG' make; ./main
> Counter 1: 0
> Counter 2: 4
> Debug all counters: 7
main.cpp
:
#include<iostream>
#include "counters.hpp"
#include "increment1.hpp"
#include "increment2.hpp"
using namespace std;
int main(int argc, char *argv[]){
Counters c;
increment1(c);increment1(c);increment1(c);
increment2(c);increment2(c);increment2(c);increment2(c);
cout << "Counter 1: " << c.getCounter1() << endl;
cout << "Counter 2: " << c.getCounter2() << endl;
#ifndef NDEBUG
cout << "Debug all counters: " << c.getDebugAllCounters() << endl;
#endif
return 0;}
Purportedly, this is because when increment1.cpp
receives the "no-debug" version of the Counters
class and is therefore unaware of the extra debug counter, which is now the 1st member of the struct, and erroneously increments it instead of m_counter
. (This is easily checked by moving the the debug counter below the two main counters)
I'm imagining that this is because clang++ -c -DNDEBUG -O2 increment1.cpp
builds increment1.cpp
(see Makefile below) with a model of Counters
which only has two (instead of three) classes, but in the final main.cpp
as well as increment2.cpp
there are three memebers. So
// increment1.cpp
#include "counters.hpp"
void increment1(Counters& counters){counters.inc1();}
actually turns into pseudocode
// ..
// .. whole non-debug version of Counters
// ..
void increment1(Counters& counters){
[the Counters instance]->[first_member]++;}
}
Q1
How does this inlining of the class method into a function call take place? Does the differing names of members not matter in this case because inlining takes place at the register level? Is there a compiler flag (like -S
) I can use to see this intermediate inlined representation?
Q2
This error doesn't go away even if i replace all -O2
's with -O0
's in the Makefile
or add __attribute__((optnone))
to inc1
. If inlining is the culprit, what am i missing?
Counters.hpp
increment1.cpp
increment1.hpp
increment2.cpp
increment2.hpp
main.cpp
Counters.hpp
class Counters
{
private:
#ifndef NDEBUG
int m_debugAllCounters;
#endif
int m_counter1;
int m_counter2;
public:
Counters() :
#ifndef NDEBUG
m_debugAllCounters(0),
#endif
m_counter1(0), m_counter2(0){}
void inc1(){
#ifndef NDEBUG
m_debugAllCounters++;
#endif
m_counter1++;}
void inc2(){
#ifndef NDEBUG
m_debugAllCounters++;
#endif
m_counter2++; }
int getCounter1() const { return m_counter1; }
int getCounter2() const { return m_counter2; }
#ifndef NDEBUG
int getDebugAllCounters() const { return m_debugAllCounters; }
#endif
};
increment1.hpp
class Counters;
int increment1(Counters&);
increment1.cpp
#include "counters.hpp"
void increment1(Counters& counters){counters.inc1();}
increment2.hpp
class Counters;
int increment2(Counters&);
increment2.cpp
#include "counters.hpp"
void increment2(Counters& counters){counters.inc2();}
Makefile
all: main.o increment1.o increment2.o
clang++ -o main main.o increment1.o increment2.o
main.o: main.cpp increment1.hpp increment2.hpp counters.hpp
clang++ -c -O2 main.cpp
increment1.o: increment1.cpp counters.hpp
clang++ -c $(CFLAGS) -O2 increment1.cpp
increment2.o: increment2.cpp counters.hpp
clang++ -c -O2 increment2.cpp
clean:
rm -f *.o diff-flags
This bug is outlined in the following article("Compiling with different flags" section at the bottom).