I have been bitten by a nasty "one definition rule" violation. I am now afraid of having lots of subtle bugs in my projects.
For example, the following program will result in a null pointer dereference with visual studio 2015:
Source1.cpp:
----------
struct S {
double d = 0;
};
void Foo() {
S s;
}
Source2.cpp:
-----------
struct S {
int a = 0;
};
int main() {
int value = 5;
int& valueRef = value;
S s; // valueRef is erased due to S::d initialization from Source1.cpp
valueRef++; // crash
}
This compiles with no warnings.
It's nasty because Source2.cpp
doesn't even use anything from Source1.cpp
. If I remove Source1.cpp
from the project, it still compiles, and there is no problem anymore.
In large projects, it seems very hard to ensure that no cpp file "locally" define a struct or class with an already defined name.
I have some classes such as Point
, Serie
, State
, Item
, ... I though this was OK in small cpp files, but I realize it's not safe.
Is there a compiler warning to catch such bugs ? If not, what are the best practices to avoid ODR violation ?
The One Definition Rule (ODR) is an important rule of the C++ programming language that prescribes that objects and non-inline functions cannot have more than one definition in the entire program and template and types cannot have more than one definition by translation unit.
10. +1 for the succinct opening sentence: "In plain word, odr-used means something(variable or function) is used in a context where the definition of it must be present."
If an object, a reference or a function is odr-used, its definition must exist somewhere in the program; a violation of that is usually a link-time error. struct S { static const int x = 0; // static data member // a definition outside of class is required if it is odr-used }; const int& f(const int& r); int n = b ? (
cppcheck will detect ODR violations, and can be integrated into Visual Studio.
Here's a quick command line invocation using cppcheck ver 2.6 to find an ODR:
cppcheck classes/config/foo.cpp
Output:
classes/config/foo.h:15:1: error: The one definition rule is violated, different classes/structs have the same name 'MyClass' [ctuOneDefinitionRuleViolation]
class MyClass
^
classes/config/foo.h:60:1: note: The one definition rule is violated, different classes/structs have the same name 'MyClass'
class MyClass
^
classes/config/foo.h:15:1: note: The one definition rule is violated, different classes/structs have the same name 'MyClass'
class MyClass
^
Note that properly catching all ODRs requires the ability to see the full application source at once--full program analysis. So, cppcheck and other static analyzers may give false positives or miss some violations. However, this is an easy FOSS way to hunt for possibly problematic program constructs.
In this particular case, at the very bottom the ODR violation (which actually leads to the problem you are observing) is the implicitly-defined inline constructor of class S
. Your program has two non-matching versions of inline S::S()
function, which can be seen as another ODR violation induced by the original ODR violation (i.e. same class defined differently).
It would be difficult for the implementation to "see" this error in the current approach to the C++ compilation infrastructure. Of course, it is possible to do with sufficient effort.
In this case in order to make the error "visible" you can explicitly declare and define the class constructor as a non-inline function with empty body. Presence of two non-inline S::S()
will trigger a linker error.
Understandably, you might see this as an overly artificial measure, unacceptable in some cases since it might change the "aggregate" status of the class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With