What Exactly Is One Definition Rule in C++

What exactly is One Definition Rule in C++?

The truth is in the standard (3.2 One definition rule) :

No translation unit shall contain more
than one definition of any variable,
function, class type, enumeration type
or template.

[...]

Every program shall contain exactly
one definition of every non-inline
function or object that is used in
that program
; no diagnostic required.
The definition can appear explicitly
in the program, it can be found in the
standard or a user-defined library, or
(when appropriate) it is implicitly
defined (see 12.1, 12.4 and 12.8). An
inline function shall be defined in
every translation unit in which it is
used.

Does C have One Definition Rule like C++?

I think what you're looking for is chapter §6.2.7 from the C11 standard, Compatible type and composite type, (emphasis mine)

All declarations that refer to the same object or function shall have compatible type;
otherwise, the behavior is undefined.

and related to compatible type,

Two types have compatible type if their types are the same.

In your case, int and unsigned int are not compatible types. Hence undefined behavior.

Just to add a bit of clarity, in your source 2, unsigned int var_global; is a declaration, and it does not match the other declatation (and definition), so, this is UB.

That said, a statement like

 printf("%d \n",var_global);

will always consider the argument to %d to be of type int. In case the type and the format specifier does not match, you'll again invoke undefined behavior.


EDIT:

After the edit, the answer is, use -fno-common to get the desired error. (missing extern is what you're bothered with, I believe).

Quoting from online GCC manual,

-fno-common

In C code, controls the placement of uninitialized global variables. Unix C compilers have traditionally permitted multiple definitions of such variables in different compilation units by placing the variables in a common block. This is the behavior specified by -fcommon, and is the default for GCC on most targets. On the other hand, this behavior is not required by ISO C, and on some targets may carry a speed or code size penalty on variable references. The -fno-common option specifies that the compiler should place uninitialized global variables in the data section of the object file, rather than generating them as common blocks. This has the effect that if the same variable is declared (without extern) in two different compilations, you get a multiple-definition error when you link them. In this case, you must compile with -fcommon instead. Compiling with -fno-common is useful on targets for which it provides better performance, or if you wish to verify that the program will work on other systems that always treat uninitialized variable declarations this way.


I don't know of any mention of the wordings "one definition rule" in the C standard, but along the line, you can look into annex §J.5.11, Multiple external definitions,

There may be more than one external definition for the identifier of an object, with or
without the explicit use of the keyword extern; if the definitions disagree, or more than
one is initialized, the behavior is undefined.

One definition rule in c++

This doesn't break the rule because you define two different variables. They have the same name, but are declared in different scopes, and so are separate entities. Each has a single definition.

The declaration in the function's scope is said to hide the one in the global namespace. Within the function, the unqualified name a refers to the local variable, while the qualified name ::a refers to the global.

Why One Definition Rule, not One Declaration Rule?

Definition is a subset of declaration, not the other way around. Every definition is a declaration, and there are declarations that are not definitions.

int i = 3;      // definition and declaration
extern int i; // ok: (re)declaration
int i = 4; // error: redefinition

extern int j; // declaration
extern int j; // ok: (re)declaration
int j = 5; // ok: (re)declaration and definition
int j = 6; // error: redefinition

Why isn't the one definition rule abandoned for C++17?

"Turning off" the ODR with inline is not free: the definition of an inline entity must be present in every translation unit. Note that this implies that any change to its definition causes re-compilation of every compilation unit that uses it. This would be particularly unpleasant when the function is part of some library many / big projects depend upon.

Non-inline functions, on the other hand, live in just one compilation unit and are referenced via some symbol by the linker when needed elsewhere. Complying with the ODR guarantees that symbol is not ambiguous.

One Definition Rule - compilation

Unless you include both definitions in the same file, there is no problem. This is because the compiler operates on a single translation unit which is usually a .cpp file. Everything you #include in this file is also part of the translation unit because the preprocessor basically copies and pastes the contents of all included files.

What happens is that the compiler will create and object file (.obj usually) for each translation unit and then the linker will create a single executable (or .dll etc) by linking all the object files and the libraries the project depends on. In your case the compiler encountered each struct in a different translation unit so it doesn't see a problem. When you include both files, the two definitions now find themselves in the same translation unit and the compiler throws an error because it cannot resolve the ambiguity if an S is used in this translation unit (even though you don't have to use one for the program to be ill-formed).

As a side-note, do not include .cpp files in other .cpp files. I'm sure you can find a lot on how to organize your code in header and source files and it doesn't directly answer the question so I won't expand on it.

EDIT: I neglected to say why you didn't get a linker error. Some comments have pointed out that this is undefined behavior which means that even though your linker should probably complain it doesn't actually have to. In your case you have one .obj file for each struct and a main.obj. None of these references the other so the linker does not see any references that it needs to resolve and it probably doesn't bother checking for ambiguous symbols.

I assume most linkers would throw an error if you declared struct S; and tried to use a S* or S& (an actual S would require definition inside the same translation unit). That is because the linker would need to resolve that symbol and it would find two matching definitions. Given that this is undefined, though, a standard-compliant linker could just pick one and silently link your program into something nonsensical because you meant to use the other. This can be especially dangerous for structs that get passed around from one .cpp to the other as the definition needs to be consistent. It might also be a problem when identically named structs/classes are passed through library boundaries. Always avoid duplicating names for these reasons.

One definition rule warning

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.

inline functions and the one definition rule

In general, you can't do any of this stuff safely. There's only two ways to safely use say two definitions of a class. Trivially you can simply have two separate processes compiled differently that communicate via e.g. shared memory. Less trivially, you can use two libraries that define the same symbol A in two different way if:

  • the symbol A is only an implementation detail of the library; it cannot be provided by the library nor appear in any interfaces
  • Along these lines, none of the library's header files should transitively include A's header. So client translation units will not receive any definition of A from the library.
  • the visibility of the symbol A must be marked private/hidden.

If you do all that, then A is truly an implementation detail of the library, and you can use multiple libraries that define A differently. If any of these is not satisfied, then you can't guarantee that any of the above will work (though some will).

One of the most surprising outcomes for those not familiar with the linker, is that if lib1 and lib2 both use symbol A, even if they stop any leakage through the headers, if the visibility of A is public then a single definition of A will be used across both libraries. So either lib2 will use lib1's definition, or vice versa. This can rather easily lead to UB.

On *nix systems, public visibilility is the default so you will need to be sure to go out of your way to hide the symbol, which is a bit arcane.

C++ confusion about one definition rule

It's just as it says. You defined the same class S twice, with different definitions. The makers of the language have declared that you shall not do this. The reason is that allowing it would be clearly nonsensical, and result in breaking compatibility across your translation units. Which definition is the "right" one? Which should your compiler use?

An unnamed namespace results in the two definitions actually defining different classes S, which are properly named something akin to my-anonymous-namespace-1::S and my-anonymous-namespace-2::S, though you can never refer to them like that because the namespaces are, well, anonymous.



Related Topics



Leave a reply



Submit